mirror of
https://github.com/status-im/nim-stew.git
synced 2025-01-24 02:50:18 +00:00
Refactor the bitranges module
* The bit procs defined over number values and openarray are now part of the bitops2 module and use the more traditional LittleEndian indexing. * Added BitSeq and BitArray types as defined in the ETH2 spec.
This commit is contained in:
parent
f782f0378f
commit
3fce87f0f5
146
stew/bitops2.nim
146
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()
|
||||
|
||||
|
90
stew/bitseqs.nim
Normal file
90
stew/bitseqs.nim
Normal file
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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":
|
||||
|
Loading…
x
Reference in New Issue
Block a user