Implement toHex/fromHex and fix `shl`

This commit is contained in:
Mamy André-Ratsimbazafy 2020-06-13 16:44:13 +02:00 committed by jangko
parent 2ac1ee3f1e
commit 777a84e9f5
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
5 changed files with 285 additions and 101 deletions

View File

@ -9,27 +9,131 @@
import private/datatypes
import stew/endians2
export endians2
{.push raises: [IndexError], noInit, gcsafe.}
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = system.cpuEndian):
array[bits div 8, byte] {.inline.} =
when endian == system.cpuEndian:
for i in 0 ..< x.limbs.len:
result[i * sizeof(Word)] = x.limbs[i].toBytes()
# Serialization
# ------------------------------------------------------------------------------------------
template toByte(x: SomeUnsignedInt): byte =
## At compile-time, conversion to bytes checks the range
## we want to ensure this is done at the register level
## at runtime in a single "mov byte" instruction
when nimvm:
byte(x and 0xFF)
else:
for i in 0 ..< x.limbs.len:
result[i * sizeof(Word)] = x.limbs[^i].toBytes()
byte(x)
func toBytesLE*[bits: static int](x: StUint[bits]):
array[bits div 8, byte] {.inline.} =
toBytes(x, littleEndian)
template blobFrom(dst: var openArray[byte], src: SomeUnsignedInt, startIdx: int, endian: static Endianness) =
## Write an integer into a raw binary blob
## Swapping endianness if needed
when endian == cpuEndian:
for i in 0 ..< sizeof(src):
dst[startIdx+i] = toByte((src shr (i * 8)))
else:
for i in 0 ..< sizeof(src):
dst[startIdx+sizeof(src)-1-i] = toByte((src shr (i * 8)))
func toBytesBE*[bits: static int](x: StUint[bits]):
array[bits div 8, byte] {.inline.} =
toBytes(x, bigEndian)
func toBytesLE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] =
var
src_idx, dst_idx = 0
acc: Word = 0
acc_len = 0
when cpuEndian == bigEndian:
srcIdx = src.limbs.len - 1
var tail = result.len
while tail > 0:
when cpuEndian == littleEndian:
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
else: 0
inc src_idx
else:
let w = if src_idx >= 0: src.limbs[src_idx]
else: 0
dec src_idx
if acc_len == 0:
# We need to refill the buffer to output 64-bit
acc = w
acc_len = WordBitWidth
else:
let lo = acc
acc = w
if tail >= sizeof(Word):
# Unrolled copy
result.blobFrom(src = lo, dst_idx, littleEndian)
dst_idx += sizeof(Word)
tail -= sizeof(Word)
else:
# Process the tail and exit
when cpuEndian == littleEndian:
# When requesting little-endian on little-endian platform
# we can just copy each byte
# tail is inclusive
for i in 0 ..< tail:
result[dst_idx+i] = toByte(lo shr (i*8))
else: # TODO check this
# We need to copy from the end
for i in 0 ..< tail:
result[dst_idx+i] = toByte(lo shr ((tail-i)*8))
return
func toBytesBE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] {.inline.} =
var
src_idx = 0
acc: Word = 0
acc_len = 0
when cpuEndian == bigEndian:
srcIdx = src.limbs.len - 1
var tail = result.len
while tail > 0:
when cpuEndian == littleEndian:
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
else: 0
inc src_idx
else:
let w = if src_idx >= 0: src.limbs[src_idx]
else: 0
dec src_idx
if acc_len == 0:
# We need to refill the buffer to output 64-bit
acc = w
acc_len = WordBitWidth
else:
let lo = acc
acc = w
if tail >= sizeof(Word):
# Unrolled copy
tail -= sizeof(Word)
result.blobFrom(src = lo, tail, bigEndian)
else:
# Process the tail and exit
when cpuEndian == littleEndian:
# When requesting little-endian on little-endian platform
# we can just copy each byte
# tail is inclusive
for i in 0 ..< tail:
result[tail-1-i] = toByte(lo shr (i*8))
else:
# We need to copy from the end
for i in 0 ..< tail:
result[tail-1-i] = toByte(lo shr ((tail-i)*8))
return
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = system.cpuEndian): array[bits div 8, byte] {.inline.} =
if endian == littleEndian:
result = x.toBytesLE()
else:
result = x.toBytesBE()
# Deserialization
# ------------------------------------------------------------------------------------------
func fromBytesBE*[bits: static int](
T: typedesc[StUint[bits]],

View File

@ -8,12 +8,18 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
typetraits, algorithm, hashes,
# Status libraries
# stew/byteutils,
# Internal
./private/datatypes,
# ./private/int_negabs,
# ./private/compiletime_helpers,
# ./intops,
./uintops, ./endians2,
typetraits, algorithm, hashes
./uintops, ./endians2
from stew/byteutils import toHex # Why are we exporting readHexChar in byteutils?
template static_check_size(T: typedesc[SomeInteger], bits: static[int]) =
# To avoid a costly runtime check, we refuse storing into StUint types smaller
@ -356,44 +362,20 @@ func hexToUint*[bits: static[int]](hexString: string): StUint[bits] {.inline.} =
# ## Leading zeros are stripped. Use dumpHex instead if you need the in-memory representation
# toString(num, 16)
# func dumpHex*(x: Stint or StUint, order: static[Endianness] = bigEndian): string =
# ## Stringify an int to hex.
# ## Note. Leading zeros are not removed. Use toString(n, base = 16)/toHex instead.
# ##
# ## You can specify bigEndian or littleEndian order.
# ## i.e. in bigEndian:
# ## - 1.uint64 will be 00000001
# ## - (2.uint128)^64 + 1 will be 0000000100000001
# ##
# ## in littleEndian:
# ## - 1.uint64 will be 01000000
# ## - (2.uint128)^64 + 1 will be 0100000001000000
# const
# hexChars = "0123456789abcdef"
# size = bitsof(x.data) div 8
# result = newString(2*size)
# when nimvm:
# for i in 0 ..< size:
# when order == system.cpuEndian:
# let byte = x.data.getByte(i)
# else:
# let byte = x.data.getByte(size - 1 - i)
# result[2*i] = hexChars[int byte shr 4 and 0xF]
# result[2*i+1] = hexChars[int byte and 0xF]
# else:
# {.pragma: restrict, codegenDecl: "$# __restrict $#".}
# let bytes {.restrict.}= cast[ptr array[size, byte]](x.unsafeaddr)
# for i in 0 ..< size:
# when order == system.cpuEndian:
# result[2*i] = hexChars[int bytes[i] shr 4 and 0xF]
# result[2*i+1] = hexChars[int bytes[i] and 0xF]
# else:
# result[2*i] = hexChars[int bytes[bytes[].high - i] shr 4 and 0xF]
# result[2*i+1] = hexChars[int bytes[bytes[].high - i] and 0xF]
func dumpHex*(a: Stint or StUint, order: static[Endianness] = bigEndian): string =
## Stringify an int to hex.
## Note. Leading zeros are not removed. Use toString(n, base = 16)/toHex instead.
##
## You can specify bigEndian or littleEndian order.
## i.e. in bigEndian:
## - 1.uint64 will be 00000001
## - (2.uint128)^64 + 1 will be 0000000100000001
##
## in littleEndian:
## - 1.uint64 will be 01000000
## - (2.uint128)^64 + 1 will be 0100000001000000
let bytes = a.toBytes(order)
result = bytes.toHex()
proc initFromBytesBE*[bits: static[int]](val: var Stuint[bits],
ba: openarray[byte],

View File

@ -11,7 +11,8 @@ import
./datatypes,
./primitives/extended_precision
# ################### Multiplication ################### #
# Multiplication
# --------------------------------------------------------
{.push raises: [], gcsafe.}
func prod*[rLen, aLen, bLen: static int](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) =

View File

@ -0,0 +1,93 @@
# Stint
# Copyright 2018-Present Status Research & Development GmbH
# Licensed under either of
#
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./datatypes
# Shifts
# --------------------------------------------------------
{.push raises: [], gcsafe.}
func shrSmall*(r: var Limbs, a: Limbs, k: SomeInteger) =
## Shift right by k.
##
## k MUST be less than the base word size (2^32 or 2^64)
# Note: for speed, loading a[i] and a[i+1]
# instead of a[i-1] and a[i]
# is probably easier to parallelize for the compiler
# (antidependence WAR vs loop-carried dependence RAW)
when cpuEndian == littleEndian:
for i in 0 ..< a.len-1:
r[i] = (a[i] shr k) or (a[i+1] shl (WordBitWidth - k))
r[^1] = a[^1] shr k
else:
for i in countdown(a.len-1, 1):
r[i] = (a[i] shr k) or (a[i-1] shl (WordBitWidth - k))
r[0] = a[0] shr k
func shrLarge*(r: var Limbs, a: Limbs, w, shift: SomeInteger) =
## Shift right by `w` words + `shift` bits
## Assumes `r` is 0 initialized
if w > Limbs.len:
return
when cpuEndian == littleEndian:
for i in w ..< a.len-1:
r[i-w] = (a[i] shr shift) or (a[i+1] shl (WordBitWidth - shift))
r[^w] = a[^1] shr shift
else:
for i in countdown(a.len-1, 1+w):
r[i-w] = (a[i] shr shift) or (a[i-1] shl (WordBitWidth - k))
r[0] = a[w] shr shift
func shrWords*(r: var Limbs, a: Limbs, w: SomeInteger) =
## Shift right by w word
when cpuEndian == littleEndian:
for i in 0 ..< Limbs.len-w:
r[i] = a[i+w]
else:
for i in countdown(Limbs.len-w, 0):
r[i] = a[i+w]
func shlSmall*(r: var Limbs, a: Limbs, k: SomeInteger) =
## Compute the `shift left` operation of x and k
##
## k MUST be less than the base word size (2^32 or 2^64)
when cpuEndian == littleEndian:
r[0] = a[0] shl k
for i in 1 ..< a.len:
r[i] = (a[i] shl k) or (a[i-1] shr (WordBitWidth - k))
else:
r[^1] = a[^1] shl k
for i in countdown(a.len-2, 0):
r[i] = (a[i] shl k) or (a[i+1] shr (WordBitWidth - k))
func shlLarge*(r: var Limbs, a: Limbs, w, shift: SomeInteger) =
## Shift left by `w` words + `shift` bits
## Assumes `r` is 0 initialized
if w > Limbs.len:
return
when cpuEndian == littleEndian:
r[w] = a[0] shl shift
for i in 1+w ..< r.len:
r[i] = (a[i-w] shl shift) or (a[i-w-1] shr (WordBitWidth - shift))
else:
r[^1] = a[^w] shl shift
for i in countdown(a.len-2-w, 0):
r[i+w] = (a[i] shl shift) or (a[i+1] shr (WordBitWidth - shift))
func shlWords*(r: var Limbs, a: Limbs, w: SomeInteger) =
## Shift left by w word
when cpuEndian == littleEndian:
for i in 0 ..< Limbs.len-w:
r[i+w] = a[i]
else:
for i in countdown(Limbs.len-1, 0):
r[i] = a[i-w]

View File

@ -12,6 +12,7 @@ import
stew/bitops2,
# Internal
./private/datatypes,
./private/uint_shift,
./private/primitives/addcarry_subborrow
export StUint
@ -127,69 +128,72 @@ func `xor`*(a, b: Stuint): Stuint =
wr = wa xor wb
result.clearExtraBits()
func `shr`*(a: Stuint, k: SomeInteger): Stuint =
## Shift right by k.
##
## k MUST be less than the base word size (2^32 or 2^64)
# Note: for speed, loading a[i] and a[i+1]
# instead of a[i-1] and a[i]
# is probably easier to parallelize for the compiler
# (antidependence WAR vs loop-carried dependence RAW)
when cpuEndian == littleEndian:
for i in 0 ..< a.limbs.len-1:
result.limbs[i] = (a.limbs[i] shr k) or (a.limbs[i+1] shl (WordBitWidth - k))
result.limbs[^1] = a.limbs[^1] shr k
else:
for i in countdown(a.limbs.len-1, 1):
result.limbs[i] = (a.limbs[i] shr k) or (a.limbs[i-1] shl (WordBitWidth - k))
result.limbs[0] = a.limbs[0] shr k
func `shl`*(a: Stuint, k: SomeInteger): Stuint =
## Compute the `shift left` operation of x and k
when cpuEndian == littleEndian:
result.limbs[0] = a.limbs[0] shl k
for i in 1 ..< a.limbs.len:
result.limbs[i] = (a.limbs[i] shl k) or (a.limbs[i-1] shr (WordBitWidth - k))
else:
result.limbs[^1] = a.limbs[^1] shl k
for i in countdown(a.limbs.len-2, 0):
result.limbs[i] = (a.limbs[i] shl k) or (a.limbs[i+1] shr (WordBitWidth - k))
result.clearExtraBits()
func countOnes*(x: Stuint): int {.inline.} =
func countOnes*(a: Stuint): int {.inline.} =
result = 0
for wx in leastToMostSig(x):
result += countOnes(wx)
for wa in leastToMostSig(a):
result += countOnes(wa)
func parity*(x: Stuint): int {.inline.} =
result = parity(x.limbs[0])
for i in 1 ..< x.limbs.len:
result = result xor parity(x.limbs[i])
func parity*(a: Stuint): int {.inline.} =
result = parity(a.limbs[0])
for i in 1 ..< a.limbs.len:
result = result xor parity(a.limbs[i])
func leadingZeros*(x: Stuint): int {.inline.} =
func leadingZeros*(a: Stuint): int {.inline.} =
result = 0
for word in mostToLeastSig(x):
for word in mostToLeastSig(a):
let zeroCount = word.leadingZeros()
result += zeroCount
if zeroCount != WordBitWidth:
return
func trailingZeros*(x: Stuint): int {.inline.} =
func trailingZeros*(a: Stuint): int {.inline.} =
result = 0
for word in leastToMostSig(x):
for word in leastToMostSig(a):
let zeroCount = word.leadingZeros()
result += zeroCount
if zeroCount != WordBitWidth:
return
func firstOne*(x: Stuint): int {.inline.} =
result = trailingZeros(x)
if result == x.limbs.len * WordBitWidth:
func firstOne*(a: Stuint): int {.inline.} =
result = trailingZeros(a)
if result == a.limbs.len * WordBitWidth:
result = 0
else:
result += 1
{.pop.}
func `shr`*(a: Stuint, k: SomeInteger): Stuint {.inline.} =
## Shift right by k bits
if k < WordBitWidth:
result.limbs.shrSmall(a.limbs, k)
return
# w = k div WordBitWidth, shift = k mod WordBitWidth
let w = k shr static(log2trunc(uint32(WordBitWidth)))
let shift = k and (WordBitWidth - 1)
if shift == 0:
result.limbs.shrWords(a.limbs, w)
else:
result.limbs.shrLarge(a.limbs, w, shift)
func `shl`*(a: Stuint, k: SomeInteger): Stuint {.inline.} =
## Shift left by k bits
if k < WordBitWidth:
result.limbs.shlSmall(a.limbs, k)
result.clearExtraBits()
return
# w = k div WordBitWidth, shift = k mod WordBitWidth
let w = k shr static(log2trunc(uint32(WordBitWidth)))
let shift = k and (WordBitWidth - 1)
if shift == 0:
result.limbs.shlWords(a.limbs, w)
else:
result.limbs.shlLarge(a.limbs, w, shift)
result.clearExtraBits()
{.pop.} # End inline
# Addsub
# --------------------------------------------------------
{.push raises: [], inline, noInit, gcsafe.}