diff --git a/stew/bitops2.nim b/stew/bitops2.nim index 9143d02..ae62f6d 100644 --- a/stew/bitops2.nim +++ b/stew/bitops2.nim @@ -25,6 +25,8 @@ const template bitsof*(T: typedesc[SomeInteger]): int = 8 * sizeof(T) template bitsof*(x: SomeInteger): int = 8 * sizeof(x) +type BitIndexable = SomeUnsignedInt + # #### Pure Nim version #### func nextPow2Nim(x: SomeUnsignedInt): SomeUnsignedInt = @@ -394,3 +396,147 @@ func rotateRight*(v: SomeUnsignedInt, amount: SomeInteger): const mask = bitsof(v) - 1 let amount = int(amount and mask) (v shr amount) or (v shl ( (-amount) and mask)) + +template mostSignificantBit(T: type): auto = + const res = 1 shl (sizeof(T) * 8 - 1) + T(res) + +template getBit*(x: BitIndexable, bit: Natural): bool = + ## reads a bit from `x`, assuming 0 to be the position of the + ## least significant bit + type T = type(x) + (x and T(0b1 shl bit)) != 0 + +template getBitLE*(x: BitIndexable, bit: Natural): bool = + getBit(x, bit) + +template getBitBE*(x: BitIndexable, bit: Natural): bool = + ## Reads a bit from `x`, assuming 0 to be the position of + ## the most significant bit. + ## + ## This indexing may be natural when you are considering the + ## string representation of a bit field. For example, 72 can + ## be written in binary as 0b01001000. The first bit here is + ## zero, while the second bit is one. + ## + ## Since the string representation will depend on the size of + ## the operand, using `getBitBE` with the same numeric value + ## and a bit position may produce different results depending + ## on the machine type used to store the value. For this reason, + ## this indexing scheme is considered more error-prone and + ## `getBitLE` is considering the default indexing scheme. + (x and mostSignificantBit(x.type) shr bit) != 0 + +proc setBit*(x: var BitIndexable, bit: Natural, val: bool) = + ## writes a bit in `x`, assuming 0 to be the position of the + ## least significant bit + type T = type(x) + let mask = T(0b1 shl bit) + if val: + x = x or mask + else: + x = x and not mask + +proc setBitLE*(x: var BitIndexable, bit: Natural, val: bool) = + setBit(x, bit, val) + +proc setBitBE*(x: var BitIndexable, bit: Natural, val: bool) = + ## writes a bit in `x`, assuming 0 to be the position of the + ## most significant bit + let mask = mostSignificantBit(x.type) shr bit + if val: + x = x or mask + else: + x = x and not mask + +proc raiseBit*(x: var BitIndexable, bit: Natural) = + ## raises bit in `x`, assuming 0 to be the position of the + ## least significant bit + type T = type(x) + let mask = T(0b1 shl bit) + x = x or mask + +proc raiseBitLE*(x: var BitIndexable, bit: Natural) = + raiseBit(x, bit) + +proc raiseBitBE*(x: var BitIndexable, bit: Natural) = + ## raises a bit in `x`, assuming 0 to be the position of the + ## most significant bit + type T = type(x) + let mask = mostSignificantBit(x.type) shr bit + x = x or mask + +proc lowerBit*(x: var BitIndexable, bit: Natural) = + ## raises bit in a byte, assuming 0 to be the position of the + ## least significant bit + type T = type(x) + let mask = T(0b1 shl bit) + x = x and not mask + +proc lowerBitLE*(x: var BitIndexable, bit: Natural) = + lowerBit(x, bit) + +proc lowerBitBE*(x: var BitIndexable, bit: Natural) = + ## raises a bit in `x`, assuming 0 to be the position of the + ## most significant bit + type T = type(x) + let mask = mostSignificantBit(x.type) shr bit + x = x and not mask + +template byteIndex(pos: Natural): int = + pos shr 3 # same as pos div 8 + +template bitIndex(pos: Natural): int = + pos and 0b111 # same as pos mod 8 + +proc getBit*(bytes: openarray[byte], pos: Natural): bool {.inline.} = + getBit(bytes[byteIndex pos], bitIndex pos) + +proc getBitBE*(bytes: openarray[byte], pos: Natural): bool {.inline.} = + getBitBE(bytes[byteIndex pos], bitIndex pos) + +template getBitLE*(bytes: openarray[byte], pos: Natural): bool = + getBit(bytes, pos) + +proc setBit*(bytes: var openarray[byte], pos: Natural, value: bool) {.inline.} = + setBit(bytes[byteIndex pos], bitIndex pos, value) + +proc setBitBE*(bytes: var openarray[byte], pos: Natural, value: bool) {.inline.} = + setBitBE(bytes[byteIndex pos], bitIndex pos, value) + +template getBitLE*(bytes: var openarray[byte], pos: Natural, value: bool) = + setBitLE(bytes, pos) + +proc lowerBit*(bytes: var openarray[byte], pos: Natural) {.inline.} = + lowerBit(bytes[byteIndex pos], bitIndex pos) + +proc lowerBitBE*(bytes: var openarray[byte], pos: Natural) {.inline.} = + lowerBitBE(bytes[byteIndex pos], bitIndex pos) + +template lowerBitLE*(bytes: var openarray[byte], pos: Natural) = + lowerBit(bytes, pos) + +proc raiseBit*(bytes: var openarray[byte], pos: Natural) {.inline.} = + raiseBit(bytes[byteIndex pos], bitIndex pos) + +proc raiseBitBE*(bytes: var openarray[byte], pos: Natural) {.inline.} = + raiseBitBE(bytes[byteIndex pos], bitIndex pos) + +template raiseBitLE*(bytes: var openarray[byte], pos: Natural) = + raiseBit(bytes, pos) + +when isMainModule: + template test() = + doAssert countOnes(0b01000100'u8) == 2 + doAssert parity(0b00000001'u8) == 1 + doAssert firstOne(0b00000010'u8) == 2 + doAssert firstOne(0'u8) == 0 + doAssert log2trunc(0b01000000'u8) == 6 + doAssert leadingZeros(0b00100000'u8) == 2 + doAssert trailingZeros(0b00100000'u8) == 5 + doAssert leadingZeros(0'u8) == 8 + doAssert trailingZeros(0'u8) == 8 + + test() + static: test() + diff --git a/stew/bitseqs.nim b/stew/bitseqs.nim new file mode 100644 index 0000000..37f544e --- /dev/null +++ b/stew/bitseqs.nim @@ -0,0 +1,90 @@ +import + bitops2 + +type + Bytes = seq[byte] + BitSeq* = distinct Bytes + + BitArray*[bits: static int] = object + bytes*: array[(bits + 7) div 8, byte] + +proc len*(s: BitSeq): int = + let + bytesCount = s.Bytes.len + lastByte = s.Bytes[bytesCount - 1] + markerPos = log2trunc(lastByte) + + Bytes(s).len * 8 - (8 - markerPos) + +template bytes*(s: BitSeq): untyped = + Bytes(s) + +proc add*(s: var BitSeq, value: bool) = + let + lastBytePos = s.Bytes.len - 1 + lastByte = s.Bytes[lastBytePos] + + if (lastByte and byte(128)) == 0: + # There is at least one leading zero, so we have enough + # room to store the new bit + let markerPos = log2trunc(lastByte) + s.Bytes[lastBytePos].setBit markerPos, value + s.Bytes[lastBytePos].raiseBit markerPos + 1 + else: + s.Bytes[lastBytePos].setBit 7, value + s.Bytes.add byte(1) + +proc `[]`*(s: BitSeq, pos: Natural): bool {.inline.} = + doAssert pos < s.len + s.Bytes.getBit pos + +proc `[]=`*(s: var BitSeq, pos: Natural, value: bool) {.inline.} = + doAssert pos < s.len + s.Bytes.setBit pos, value + +proc raiseBit*(s: var BitSeq, pos: Natural) {.inline.} = + doAssert pos < s.len + raiseBit s.Bytes, pos + +proc lowerBit*(s: var BitSeq, pos: Natural) {.inline.} = + doAssert pos < s.len + lowerBit s.Bytes, pos + +proc init*(T: type BitSeq, len: int): T = + result = BitSeq newSeq[byte](1 + len div 8) + Bytes(result).raiseBit len + +proc init*(T: type BitArray): T = + # The default zero-initializatio is fine + discard + +template `[]`*(a: BitArray, pos: Natural): bool = + getBit a.bytes, pos + +template `[]=`*(a: var BitArray, pos: Natural, value: bool) = + setBit a.bytes, pos, value + +template raiseBit*(a: var BitArray, pos: Natural) = + raiseBit a.bytes, pos + +template lowerBit*(a: var BitArray, pos: Natural) = + lowerBit a.bytes, pos + +# TODO: Submit this to the standard library as `cmp` +# At the moment, it doesn't work quite well because Nim selects +# the generic cmp[T] from the system module instead of choosing +# the openarray overload +proc compareArrays[T](a, b: openarray[T]): int = + result = cmp(a.len, b.len) + if result != 0: return + + for i in 0 ..< a.len: + result = cmp(a[i], b[i]) + if result != 0: return + +template cmp*(a, b: BitSeq): int = + compareArrays(Bytes a, Bytes b) + +template `==`*(a, b: BitSeq): bool = + cmp(a, b) == 0 + diff --git a/stew/ranges/bitranges.nim b/stew/ranges/bitranges.nim index 25e4b18..1c61ce9 100644 --- a/stew/ranges/bitranges.nim +++ b/stew/ranges/bitranges.nim @@ -1,5 +1,5 @@ import - typedranges, ptr_arith + ../bitops2, typedranges, ptr_arith type BitRange* = object @@ -7,8 +7,6 @@ type start: int mLen: int - BitIndexable = SomeUnsignedInt - template `@`(s, idx: untyped): untyped = (when idx is BackwardsIndex: s.len - int(idx) else: int(idx)) @@ -39,103 +37,17 @@ template bits*(bytes: MutByteRange, slice: HSlice): BitRange = template bits*(x: BitRange): BitRange = x -template mostSignificantBit(T: typedesc): auto = - const res = 1 shl (sizeof(T) * 8 - 1) - T(res) - -template getBit*(x: BitIndexable, bit: Natural): bool = - ## reads a bit from `x`, assuming 0 to be the position of the - ## most significant bit - (x and mostSignificantBit(x.type) shr bit) != 0 - -template getBitLE*(x: BitIndexable, bit: Natural): bool = - ## reads a bit from `x`, assuming 0 to be the position of the - ## least significant bit - type T = type(x) - (x and T(0b1 shl bit)) != 0 - -proc setBit*(x: var BitIndexable, bit: Natural, val: bool) = - ## writes a bit in `x`, assuming 0 to be the position of the - ## most significant bit - let mask = mostSignificantBit(x.type) shr bit - if val: - x = x or mask - else: - x = x and not mask - -proc setBitLE*(x: var BitIndexable, bit: Natural, val: bool) = - ## writes a bit in `x`, assuming 0 to be the position of the - ## least significant bit - type T = type(x) - let mask = 0b1 shl bit - if val: - x = x or mask - else: - x = x and not mask - -proc raiseBit*(x: var BitIndexable, bit: Natural) = - ## raises a bit in `x`, assuming 0 to be the position of the - ## most significant bit - type T = type(x) - let mask = mostSignificantBit(x.type) shr bit - x = x or mask - -proc lowerBit*(x: var BitIndexable, bit: Natural) = - ## raises a bit in `x`, assuming 0 to be the position of the - ## most significant bit - type T = type(x) - let mask = mostSignificantBit(x.type) shr bit - x = x and not mask - -proc raiseBitLE*(x: var BitIndexable, bit: Natural) = - ## raises bit in `x`, assuming 0 to be the position of the - ## least significant bit - type T = type(x) - let mask = 0b1 shl bit - x = x or mask - -proc lowerBitLE*(x: var BitIndexable, bit: Natural) = - ## raises bit in a byte, assuming 0 to be the position of the - ## least significant bit - type T = type(x) - let mask = 0b1 shl bit - x = x and not mask - proc len*(r: BitRange): int {.inline.} = r.mLen -template getAbsoluteBit(bytes, absIdx: untyped): bool = - ## Returns a bit with a position relative to the start of - ## the underlying range. Not to be confused with a position - ## relative to the start of the BitRange (i.e. the two would - ## match only when range.start == 0). - let - byteToCheck = absIdx shr 3 # the same as absIdx / 8 - bitToCheck = (absIdx and 0b111) - - getBit(bytes[byteToCheck], bitToCheck) - -template setAbsoluteBit(bytes, absIdx, value) = - let - byteToWrite = absIdx shr 3 # the same as absIdx / 8 - bitToWrite = (absIdx and 0b111) - - setBit(bytes[byteToWrite], bitToWrite, value) - iterator enumerateBits(x: BitRange): (int, bool) = var p = x.start var i = 0 let e = x.len while i != e: - yield (i, getAbsoluteBit(x.data, p)) + yield (i, getBitBE(x.data.toOpenArray, p)) inc p inc i -proc getBit*(bytes: openarray[byte], pos: Natural): bool = - getAbsoluteBit(bytes, pos) - -proc setBit*(bytes: var openarray[byte], pos: Natural, value: bool) = - setAbsoluteBit(bytes, pos, value) - iterator items*(x: BitRange): bool = for _, v in enumerateBits(x): yield v @@ -145,14 +57,14 @@ iterator pairs*(x: BitRange): (int, bool) = proc `[]`*(x: BitRange, idx: int): bool {.inline.} = doAssert idx < x.len let p = x.start + idx - result = getAbsoluteBit(x.data, p) + result = getBitBE(x.data.toOpenArray, p) proc sliceNormalized(x: BitRange, ibegin, iend: int): BitRange = doAssert ibegin >= 0 and - ibegin < x.len and - iend < x.len and - iend + 1 >= ibegin # the +1 here allows the result to be - # an empty range + ibegin < x.len and + iend < x.len and + iend + 1 >= ibegin # the +1 here allows the result to be + # an empty range result.data = x.data result.start = x.start + ibegin @@ -170,18 +82,7 @@ proc `==`*(a, b: BitRange): bool = proc `[]=`*(r: var BitRange, idx: Natural, val: bool) {.inline.} = doAssert idx < r.len let absIdx = r.start + idx - setAbsoluteBit(r.data, absIdx, val) - -proc setAbsoluteBit(x: BitRange, absIdx: int, val: bool) {.inline.} = - ## Assumes the destination bit is already zeroed. - ## Works with absolute positions similar to `getAbsoluteBit` - doAssert absIdx < x.len - let - byteToWrite = absIdx shr 3 # the same as absIdx / 8 - bitToWrite = (absIdx and 0b111) - - if val: - raiseBit x.data[byteToWrite], bitToWrite + setBitBE(r.data.toOpenArray, absIdx, val) proc pushFront*(x: var BitRange, val: bool) = doAssert x.start > 0 @@ -203,15 +104,15 @@ proc `&`*(a, b: BitRange): BitRange = var bytes = newSeq[byte](totalLen.neededBytes) result = bits(bytes, 0, totalLen) - for i in 0 ..< a.len: result.setAbsoluteBit(i, a[i]) - for i in 0 ..< b.len: result.setAbsoluteBit(i + a.len, b[i]) + for i in 0 ..< a.len: result.data.toOpenArray.setBitBE(i, a[i]) + for i in 0 ..< b.len: result.data.toOpenArray.setBitBE(i + a.len, b[i]) proc `$`*(r: BitRange): string = result = newStringOfCap(r.len) - for b in r: - result.add(if b: '1' else: '0') + for bit in r: + result.add(if bit: '1' else: '0') -proc fromBits*(T: typedesc, r: BitRange, offset, num: Natural): T = +proc fromBits*(T: type, r: BitRange, offset, num: Natural): T = doAssert(num <= sizeof(T) * 8) # XXX: Nim has a bug that a typedesc parameter cannot be used # in a type coercion, so we must define an alias here: @@ -219,12 +120,12 @@ proc fromBits*(T: typedesc, r: BitRange, offset, num: Natural): T = for i in 0 ..< num: result = (result shl 1) or TT(r[offset + i]) -proc parse*(T: typedesc[BitRange], s: string): BitRange = +proc parse*(T: type BitRange, s: string): BitRange = var bytes = newSeq[byte](s.len.neededBytes) for i, c in s: case c of '0': discard - of '1': raiseBit(bytes[i shr 3], i and 0b111) + of '1': raiseBitBE(bytes, i) else: doAssert false result = bits(bytes, 0, s.len) diff --git a/tests/ranges/tbitranges.nim b/tests/ranges/tbitranges.nim index 6edec61..3589b61 100644 --- a/tests/ranges/tbitranges.nim +++ b/tests/ranges/tbitranges.nim @@ -1,12 +1,54 @@ import random, unittest, - ../../stew/ranges/bitranges + ../../stew/ranges/bitranges, ../../stew/bitseqs proc randomBytes(n: int): seq[byte] = result = newSeq[byte](n) for i in 0 ..< result.len: result[i] = byte(rand(256)) +suite "bit sequences": + test "growing and indexing": + var b0 = BitSeq.init(0) + check b0.len == 0 + + var b10 = BitSeq.init(10) + check b10.len == 10 + + var b100 = BitSeq.init(100) + check b100.len == 100 + + var bytes = randomBytes(50) + + var bitArr: BitArray[50] + + for i in 0 ..< bytes.len: + let bit = bytes[i] < 128 + + b0.add bit + + if i < b10.len: + b10[i] = bit + else: + b10.add bit + + b100[i] = bit + bitArr[i] = bit + + check: + b0.len == i + 1 + b10.len == max(i + 1, 10) + b100.len == 100 + + for i in 0 ..< bytes.len: + let bit = bytes[i] < 128 + + check: + b0[i] == bit + b10[i] == bit + b100[i] == bit + bitArr[i] == bit + suite "bit ranges": test "basic":