Skip to content

Commit

Permalink
Add capability bit for bit manipulation, tests
Browse files Browse the repository at this point in the history
A bunch of basics and a pretty shitty implementation. I don't have
any motivation to write a better one because all this bit stuff is
soo boooring.

* Some decimal fixes/etc. Remove the weird `%` definition. Can't
  remember why I did that there.
  • Loading branch information
homonoidian committed Sep 2, 2023
1 parent 66c7850 commit e763581
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 7 deletions.
152 changes: 152 additions & 0 deletions src/novika/capabilities/impl/bit.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
module Novika::Capabilities::Impl
class Bit
include Capability

def self.id : String
"bit"
end

def self.purpose : String
"allows to interpret decimals as sequences of bits and manipulate those bits"
end

def self.on_by_default? : Bool
true
end

def inject(into target : Block)
target.at("bit:fromLeft", <<-END
( D I -- B ): leaves Index-th Bit from left in the given Decimal,
represented as `0` or `1`. Dies if Decimal has a fractional part.
The sign of decimal is ignored.
Note: we consider the *left*most bit to be the most significant bit,
and the *right*most bit the least significant bit. Leading zeroes
do not count.
```
0b00010001 0 bit:fromLeft leaves: 1
0b00010001 1 bit:fromLeft leaves: 0
0b00010001 2 bit:fromLeft leaves: 0
0b00010001 3 bit:fromLeft leaves: 0
0b00010001 4 bit:fromLeft leaves: 1
```
END
) do |_, stack|
index = stack.drop.a(Decimal).posint
decimal = stack.drop.a(Decimal).int
unless bit = decimal.nth_ms_bit?(index)
index.die("bit index out of bounds")
end
bit.onto(stack)
end

target.at("bit:fromRight", <<-END
( D I -- B ): leaves Index-th Bit from right in the given Decimal,
represented as `0` or `1`. Dies if Decimal has a fractional part.
The sign of decimal is ignored.
Note: we consider the *left*most bit to be the most significant bit,
and the *right*most bit the least significant bit. Leading zeroes
do not count.
```
0b00010001 0 bit:fromRight leaves: 1
0b00010001 1 bit:fromRight leaves: 0
0b00010001 2 bit:fromRight leaves: 0
0b00010001 3 bit:fromRight leaves: 0
0b00010001 4 bit:fromRight leaves: 1
```
END
) do |_, stack|
index = stack.drop.a(Decimal).posint
decimal = stack.drop.a(Decimal).int
decimal.nth_ls_bit(index).onto(stack)
end

target.at("bit:count", <<-END
( D -- Bc ): leaves Bit count, the number of bits in the given
Decimal. Dies if Decimal has a fractional part.
```
0b00010001 bit:count leaves: 4
```
END
) do |_, stack|
decimal = stack.drop.a(Decimal).int
decimal.bitcount.onto(stack)
end

target.at("bit:or", <<-END
( D D -- D ): combines two Decimal numbers using bitwise or, leaves
the resulting Decimal. Dies if either of decimal has a fractional part.
```
0b00010001
0b10001000 bit:or leaves:
0b10011001
```
END
) do |_, stack|
b = stack.drop.a(Decimal).int
a = stack.drop.a(Decimal).int
(a | b).onto(stack)
end

target.at("bit:and", <<-END
( D D -- D ): combines two Decimal numbers using bitwise and, leaves
the resulting Decimal. Dies if either of decimal has a fractional part.
```
0b10011001
0b00011000 bit:and leaves:
0b00011000
```
END
) do |_, stack|
b = stack.drop.a(Decimal).int
a = stack.drop.a(Decimal).int
(a & b).onto(stack)
end

target.at("bit:bits", <<-END
( D -- Bb ): leaves Bits block for the given Decimal, which contains
the binary representation of the *absolute value* of Decimal, starting
with the most-significant bit.
```
0b10011001 bit:bits leaves: [ 1 0 0 1 1 0 0 1 ]
```
END
) do |_, stack|
decimal = stack.drop.a(Decimal).int
bits = Block.new
decimal.each_bit &.onto(bits)
bits.onto(stack)
end

target.at("bit:fromBits", <<-END
( Bb -- D ): converts Bits block to a Decimal. Bits block should
contain binary digits (represented by `0` or `1`), and should
begin with the most significant bit.
```
0b10011001 bit:bits leaves: [[ 1 0 0 1 1 0 0 1 ]]
bit:fromBits leaves: 0b10011001
```
END
) do |_, stack|
bits = stack.drop.a(Block)
acc = Decimal.new(0)
pow = Decimal.new(bits.count - 1)
one = Decimal.new(1)
two = Decimal.new(2)
bits.each do |bit|
acc += bit.a(Decimal).int.in(0..1) * two ** pow
pow -= one
end
acc.onto(stack)
end
end
end
end
8 changes: 4 additions & 4 deletions src/novika/capabilities/impl/essential.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1480,8 +1480,8 @@ module Novika::Capabilities::Impl
end

target.at("fromLeft", <<-END
( B/Q/Bf I -- E ): leaves Index-th Element from the left
in Block, Quote, or Byteslice form.
( B/Q/Bf I -- E ): leaves Index-th Element from left in Block,
Quote, or Byteslice form.
```
[ 1 2 3 ] 0 fromLeft leaves: 1
Expand All @@ -1494,8 +1494,8 @@ module Novika::Capabilities::Impl
end

target.at("fromRight", <<-END
( B/Q/Bf I -- E ): leaves Index-th Element from the right
in Block, Quote, or Byteslice form.
( B/Q/Bf I -- E ): leaves Index-th Element from right in Block,
Quote, or Byteslice form.
```
[ 1 2 3 ] 0 fromRight leaves: 3
Expand Down
55 changes: 52 additions & 3 deletions src/novika/forms/decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ module Novika

# Returns the remainder of this and *other* decimal numbers.
def %(other : Decimal) : Decimal
self - other * (self / other).floor
Decimal.new(val % other.val)
end

# Raises this decimal to the power of *other*.
Expand All @@ -95,6 +95,24 @@ module Novika
val < other.val
end

# Combines this and *other* decimals using bitwise and.
def &(other : Decimal) : Decimal
Decimal.new(val.value & other.val.value)
end

# Combines this and *other* decimals using bitwise or.
def |(other : Decimal) : Decimal
Decimal.new(val.value | other.val.value)
end

# Yields each bit in this decimal.
def each_bit(& : Decimal ->)
val.value.to_s(2).each_char do |char|
next if char == '-'
yield Decimal.new(char == '1' ? 1 : 0)
end
end

# Rounds this decimal.
def round : Decimal
Decimal.new(val.round)
Expand Down Expand Up @@ -125,6 +143,25 @@ module Novika
Decimal.new(Math.sin(val))
end

# Returns *n*-th most significant bit
def nth_ms_bit?(n : Decimal) : Decimal?
str = val.value.to_s(2)
nint = n.to_i
return unless nint < str.size

Decimal.new(str[nint + (val.negative? ? 1 : 0)]? == '1' ? 1 : 0)
end

# Returns *n*-th least significant bit
def nth_ls_bit(n : Decimal) : Decimal
Decimal.new(val.value.abs.bit(n.to_i))
end

# Returns the number of bits in this decimal.
def bitcount : Decimal
Decimal.new(val.value.bit_length)
end

# Asserts this decimal is in one of *ranges*. Dies if it isn't.
def in(*ranges) : Decimal
return self if ranges.any? &.includes?(val)
Expand All @@ -143,16 +180,28 @@ module Novika
die(message)
end

# Asserts this decimal is an integer. Dies if it isn't.
def int : Decimal
return self if integer?

die("decimal is not an integer")
end

# Asserts this decimal is a positive integer (i.e., >= 0).
# Dies if it isn't.
def posint : Decimal
return self if val >= 0 && val == val.to_big_i
return self if val >= 0 && integer?

die("decimal is not a positive integer")
end

# Returns whether this decimal is an integer.
def integer?
val.trunc == val
end

def to_s(io)
io << (val.scale.zero? ? val.value : val)
io << (integer? ? val.to_big_i : val)
end

def_equals_and_hash val
Expand Down
114 changes: 114 additions & 0 deletions tests/bit.nk
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
describe 'bit:fromLeft' [
in lang

it should 'leave the indexth most significant bit, ignores sign' [
0b10001 0 bit:fromLeft 1 assert=
0b10001 1 bit:fromLeft 0 assert=
0b10001 2 bit:fromLeft 0 assert=
0b10001 3 bit:fromLeft 0 assert=
0b10001 4 bit:fromLeft 1 assert=

0b10001 flipSign 0 bit:fromLeft 1 assert=
0b10001 flipSign 1 bit:fromLeft 0 assert=
0b10001 flipSign 2 bit:fromLeft 0 assert=
0b10001 flipSign 3 bit:fromLeft 0 assert=
0b10001 flipSign 4 bit:fromLeft 1 assert=
]

it should 'die if decimal has a fractional part' [
[ 12.34 0 bit:fromLeft ] 'decimal is not an integer' assertDies
]

it should 'die if index is invalid' [
[ 0b00010001 1000 bit:fromLeft ] 'bit index out of bounds' assertDies
[ 0b00010001 -1000 bit:fromLeft ] 'decimal is not a positive integer' assertDies
[ 0b00010001 1000.123 bit:fromLeft ] 'decimal is not a positive integer' assertDies
]
]


describe 'bit:fromRight' [
in lang

it should 'leave the indexth least significant bit, ignores sign' [
0b00010001 0 bit:fromRight 1 assert=
0b00010001 1 bit:fromRight 0 assert=
0b00010001 2 bit:fromRight 0 assert=
0b00010001 3 bit:fromRight 0 assert=
0b00010001 4 bit:fromRight 1 assert=
0b00010001 5 bit:fromRight 0 assert=
0b00010001 1000 bit:fromRight 0 assert=

0b10001 flipSign 0 bit:fromRight 1 assert=
0b10001 flipSign 1 bit:fromRight 0 assert=
0b10001 flipSign 2 bit:fromRight 0 assert=
0b10001 flipSign 3 bit:fromRight 0 assert=
0b10001 flipSign 4 bit:fromRight 1 assert=
0b10001 flipSign 1000 bit:fromRight 0 assert=
]

it should 'die if decimal has a fractional part' [
[ 12.34 0 bit:fromRight ] 'decimal is not an integer' assertDies
]

it should 'die if index is invalid' [
[ 0b00010001 -1000 bit:fromRight ] 'decimal is not a positive integer' assertDies
[ 0b00010001 1000.123 bit:fromRight ] 'decimal is not a positive integer' assertDies
]
]

describe 'bit:count' [
in lang

it should 'leave the number of bits in decimal' [
0b00010001 bit:count 5 assert=
0xffffffff bit:count 32 assert=
]

it should 'die if decimal has a fractional part' [
[ 12.34 bit:count ] 'decimal is not an integer' assertDies
]
]


describe 'bit:or' [
in lang

it should 'combine two decimals using bitwise or' [
0b00010001 0b10001000 bit:or 0b10011001 assert=
]

it should 'die if any of the decimals has a fractional part' [
[ 12.34 100 bit:or ] 'decimal is not an integer' assertDies
[ 100 12.34 bit:or ] 'decimal is not an integer' assertDies
]
]

describe 'bit:and' [
in lang

it should 'combine two decimals using bitwise and' [
0b10011001 0b00011000 bit:and 0b00011000 assert=
]

it should 'die if any of the decimals has a fractional part' [
[ 12.34 100 bit:and ] 'decimal is not an integer' assertDies
[ 100 12.34 bit:and ] 'decimal is not an integer' assertDies
]
]


describe 'bit:bits/bit:fromBits' [
in lang

it should 'leave bit block of bits in a decimal' [
0b00010001 bit:bits $: bits
bits orphan? true assert=
bits [ 1 0 0 0 1 ] assert=
]

it should 'leave decimal from a block of bits' [
[ 1 0 0 0 1 ] $: bits
bits bit:fromBits 0b00010001 assert=
]
]

0 comments on commit e763581

Please sign in to comment.