2018-04-30 11:38:55 +00:00
# Stint
# Copyright 2018 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.
2018-05-06 20:29:08 +00:00
2018-04-30 11:38:55 +00:00
import
2020-06-13 14:44:13 +00:00
# Standard library
typetraits , algorithm , hashes ,
# Status libraries
# stew/byteutils,
# Internal
2018-04-30 11:38:55 +00:00
. / private / datatypes ,
2020-06-12 21:53:08 +00:00
# ./private/int_negabs,
# ./private/compiletime_helpers,
# ./intops,
2020-06-13 14:44:13 +00:00
. / uintops , . / endians2
from stew / byteutils import toHex # Why are we exporting readHexChar in byteutils?
2018-04-30 11:38:55 +00:00
template static_check_size ( T : typedesc [ SomeInteger ] , bits : static [ int ] ) =
# To avoid a costly runtime check, we refuse storing into StUint types smaller
# than the input type.
2019-03-14 02:43:51 +00:00
static : doAssert sizeof ( T ) * 8 < = bits , " Input type ( " & $ T &
2018-04-30 11:38:55 +00:00
" ) cannot be stored in a multi-precision " &
$ bits & " -bit integer. " &
" \n Use a smaller input type instead. This is a compile-time check " &
" to avoid a costly run-time bit_length check at each StUint initialization. "
func stuint * [ T : SomeInteger ] ( n : T , bits : static [ int ] ) : StUint [ bits ] {. inline . } =
2018-06-16 16:54:40 +00:00
## Converts an integer to an arbitrary precision integer.
2020-06-12 21:53:08 +00:00
when cpuEndian = = littleEndian :
result . limbs [ 0 ] = Word ( n )
when sizeof ( n ) > sizeof ( Word ) :
result . limbs [ 1 ] = Word ( n ) shr WordBitWidth
2018-04-30 11:38:55 +00:00
else :
2020-06-12 21:53:08 +00:00
result . limbs [ ^ 1 ] = Word ( n )
when sizeof ( n ) > sizeof ( Word ) :
result . limbs [ ^ 2 ] = Word ( n ) shr WordBitWidth
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
< < < < < < < HEAD
2022-02-24 19:09:53 +00:00
func to * ( x : SomeInteger , T : typedesc [ StInt ] ) : T =
2018-06-27 10:18:48 +00:00
stint ( x , result . bits )
func to * ( x : SomeUnsignedInt , T : typedesc [ StUint ] ) : T =
stuint ( x , result . bits )
2020-06-12 21:53:08 +00:00
= = = = = = =
# func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}=
# ## Converts an integer to an arbitrary precision signed integer.
#
# when result.data is IntImpl:
# static_check_size(T, bits)
# when T is SomeSignedInt:
# if n < 0:
# # TODO: when bits >= 128, cannot create from
# # low(int8-64)
# # see: status-im/nim-stint/issues/92
# assignLo(result.data, -n)
# result = -result
# else:
# assignLo(result.data, n)
# else:
# assignLo(result.data, n)
# else:
# result.data = (type result.data)(n)
# func to*(a: SomeInteger, T: typedesc[Stint]): T =
# stint(a, result.bits)
func to * ( a : SomeUnsignedInt , T : typedesc [ StUint ] ) : T =
stuint ( a , result . bits )
2018-06-27 10:18:48 +00:00
2022-02-24 19:09:53 +00:00
func truncate * ( num : StInt or StUint , T : typedesc [ SomeInteger ] ) : T {. inline . } =
2019-10-08 13:11:08 +00:00
## Extract the int, uint, int8-int64 or uint8-uint64 portion of a multi-precision integer.
2018-10-08 19:04:00 +00:00
## Note that int and uint are 32-bit on 32-bit platform.
## For unsigned result type, result is modulo 2^(sizeof T in bit)
## For signed result type, result is undefined if input does not fit in the target type.
2020-06-12 21:53:08 +00:00
result = T ( num . leastSignificantWord ( ) )
2018-10-08 19:04:00 +00:00
2022-02-24 19:09:53 +00:00
func toInt * ( num : StInt or StUint ) : int {. inline , deprecated : " Use num.truncate(int) instead " . } =
2018-10-08 19:04:00 +00:00
num . truncate ( int )
2018-10-06 09:28:15 +00:00
2020-06-12 21:53:08 +00:00
func stuint * ( a : StUint , bits : static [ int ] ) : StUint [ bits ] {. inline . } =
2019-12-10 08:57:19 +00:00
## unsigned int to unsigned int conversion
## smaller to bigger bits conversion will have the same value
## bigger to smaller bits conversion, the result is truncated
2020-06-12 21:53:08 +00:00
for wr , wa in leastToMostSig ( result , a ) :
wr = wa
# func stuint*(a: StInt, bits: static[int]): StUint[bits] {.inline.} =
# ## signed int to unsigned int conversion
# ## current behavior is cast-like, copying bit pattern
# ## or truncating if input does not fit into destination
# const N = bitsof(x.data)
# when N < bits:
# when N <= 64:
# type T = StUint[N]
# result = stuint(convert[T](a).data, bits)
# else:
# smallToBig(result.data, a.data)
# elif N > bits:
# when bits <= 64:
# result = stuint(x.truncate(type(result.data)), bits)
# else:
# bigToSmall(result.data, a.data)
# else:
# result = convert[type(result)](a)
# func stint*(a: StInt, bits: static[int]): StInt[bits] {.inline.} =
# ## signed int to signed int conversion
# ## will raise exception if input does not fit into destination
# const N = bitsof(a.data)
# when N < bits:
# when N <= 64:
# result = stint(a.data, bits)
# else:
# if a.isNegative:
# smallToBig(result.data, (-a).data)
# result = -result
# else:
# smallToBig(result.data, a.data)
# elif N > bits:
# template checkNegativeRange() =
# # due to bug #92, we skip negative range check
# when false:
# const dmin = stint((type result).low, N)
# if a < dmin: raise newException(RangeError, "value out of range")
# template checkPositiveRange() =
# const dmax = stint((type result).high, N)
# if a > dmax: raise newException(RangeError, "value out of range")
# when bits <= 64:
# if a.isNegative:
# checkNegativeRange()
# result = stint((-a).truncate(type(result.data)), bits)
# result = -result
# else:
# checkPositiveRange()
# result = stint(a.truncate(type(result.data)), bits)
# else:
# if a.isNegative:
# checkNegativeRange()
# bigToSmall(result.data, (-a).data)
# result = -result
# else:
# checkPositiveRange()
# bigToSmall(result.data, a.data)
# else:
# result = a
# func stint*(a: StUint, bits: static[int]): StInt[bits] {.inline.} =
# const N = bitsof(a.data)
# const dmax = stuint((type result).high, N)
# if a > dmax: raise newException(RangeError, "value out of range")
# when N < bits:
# when N <= 64:
# result = stint(a.data, bits)
# else:
# smallToBig(result.data, a.data)
# elif N > bits:
# when bits <= 64:
# result = stint(a.truncate(type(result.data)), bits)
# else:
# bigToSmall(result.data, a.data)
# else:
# result = convert[type(result)](a)
2019-12-10 08:57:19 +00:00
2018-04-30 11:38:55 +00:00
func readHexChar ( c : char ) : int8 {. inline . } =
## Converts an hex char to an int
case c
of ' 0 ' .. ' 9 ' : result = int8 ord ( c ) - ord ( ' 0 ' )
of ' a ' .. ' f ' : result = int8 ord ( c ) - ord ( ' a ' ) + 10
of ' A ' .. ' F ' : result = int8 ord ( c ) - ord ( ' A ' ) + 10
else :
raise newException ( ValueError , $ c & " is not a hexadecimal character " )
2018-10-08 18:17:39 +00:00
func skipPrefixes ( current_idx : var int , str : string , radix : range [ 2 .. 16 ] ) {. inline . } =
2018-04-30 11:38:55 +00:00
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
2021-10-08 08:08:56 +00:00
# Always called from a context where radix is known at compile-time
# and checked within 2..16 and so cannot throw a RangeDefect at runtime
2018-04-30 11:38:55 +00:00
2018-08-05 21:47:05 +00:00
if str . len < 2 :
return
2019-03-14 02:43:51 +00:00
doAssert current_idx = = 0 , " skipPrefixes only works for prefixes (position 0 and 1 of the string) "
2018-04-30 11:38:55 +00:00
if str [ 0 ] = = ' 0 ' :
if str [ 1 ] in { ' x ' , ' X ' } :
2022-10-03 10:15:45 +00:00
if radix = = 16 :
current_idx = 2
else :
2021-10-06 16:53:52 +00:00
raise newException ( ValueError , " Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16) " )
2018-04-30 11:38:55 +00:00
elif str [ 1 ] in { ' o ' , ' O ' } :
2022-10-03 10:15:45 +00:00
if radix = = 8 :
current_idx = 2
else :
2021-10-06 16:53:52 +00:00
raise newException ( ValueError , " Parsing mismatch, 0o prefix is only valid for an octal number (base 8) " )
2018-04-30 11:38:55 +00:00
elif str [ 1 ] in { ' b ' , ' B ' } :
2022-10-03 10:15:45 +00:00
if radix = = 2 :
current_idx = 2
elif radix ! = 16 :
raise newException ( ValueError , " Parsing mismatch, 0b prefix is only valid for a binary number (base 2) or as first byte of a hexadecimal number (base 16) " )
2018-04-30 11:38:55 +00:00
func nextNonBlank ( current_idx : var int , s : string ) {. inline . } =
## Move the current index, skipping white spaces and "_" characters.
const blanks = { ' ' , ' _ ' }
inc current_idx
2018-05-02 15:24:17 +00:00
while current_idx < s . len and s [ current_idx ] in blanks :
2018-04-30 11:38:55 +00:00
inc current_idx
2021-10-08 08:08:56 +00:00
func readDecChar ( c : char ) : int {. inline . } =
2018-04-30 11:38:55 +00:00
## Converts a decimal char to an int
# specialization without branching for base <= 10.
2021-10-08 08:08:56 +00:00
if c notin { ' 0 ' .. ' 9 ' } :
raise newException ( ValueError , " Character out of ' 0 ' .. ' 9 ' range " )
2018-04-30 11:38:55 +00:00
ord ( c ) - ord ( ' 0 ' )
2022-02-24 19:09:53 +00:00
func parse * [ bits : static [ int ] ] ( input : string , T : typedesc [ StUint [ bits ] ] , radix : static [ uint8 ] = 10 ) : T =
## Parse a string and store the result in a StInt[bits] or StUint[bits].
2018-04-30 11:38:55 +00:00
2019-03-14 02:43:51 +00:00
static : doAssert ( radix > = 2 ) and radix < = 16 , " Only base from 2..16 are supported "
2018-04-30 11:38:55 +00:00
# TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
# TODO: we can special case hex result/input as an array of bytes
# and be much faster
2018-10-08 18:17:39 +00:00
const base = radix . uint8 . stuint ( bits )
2018-04-30 11:38:55 +00:00
var curr = 0 # Current index in the string
2018-10-08 18:17:39 +00:00
skipPrefixes ( curr , input , radix )
2018-04-30 11:38:55 +00:00
while curr < input . len :
# TODO: overflow detection
2018-10-08 18:17:39 +00:00
when radix < = 10 :
result = result * base + input [ curr ] . readDecChar . stuint ( bits )
2018-04-30 11:38:55 +00:00
else :
2018-10-08 18:17:39 +00:00
result = result * base + input [ curr ] . readHexChar . stuint ( bits )
2018-04-30 11:38:55 +00:00
nextNonBlank ( curr , input )
2020-06-12 21:53:08 +00:00
# func parse*[bits: static[int]](input: string, T: typedesc[Stint[bits]], radix: static[int8] = 10): T =
# ## Parse a string and store the result in a Stint[bits] or Stuint[bits].
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# # TODO: we can special case hex result/input as an array of bytes
# # and be much faster
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# # For conversion we require overflowing operations (for example for negative hex numbers)
# const base = radix.int8.stuint(bits)
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# var
# curr = 0 # Current index in the string
# isNeg = false
# no_overflow: Stuint[bits]
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# if input[curr] == '-':
# doAssert radix == 10, "Negative numbers are only supported with base 10 input."
# isNeg = true
# inc curr
# else:
# skipPrefixes(curr, input, radix)
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# while curr < input.len:
# # TODO: overflow detection
# when radix <= 10:
# no_overflow = no_overflow * base + input[curr].readDecChar.stuint(bits)
# else:
# no_overflow = no_overflow * base + input[curr].readHexChar.stuint(bits)
# nextNonBlank(curr, input)
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# # TODO: we can't create the lowest int this way
# if isNeg:
# result = -convert[T](no_overflow)
# else:
# result = convert[T](no_overflow)
2018-04-30 11:38:55 +00:00
2022-02-24 19:09:53 +00:00
func fromHex * ( T : typedesc [ StUint | StInt ] , s : string ) : T {. inline . } =
2018-07-04 15:35:16 +00:00
## Convert an hex string to the corresponding unsigned integer
2018-10-08 18:17:39 +00:00
parse ( s , type result , radix = 16 )
2018-07-04 15:35:16 +00:00
2022-02-24 19:09:53 +00:00
func hexToUint * [ bits : static [ int ] ] ( hexString : string ) : StUint [ bits ] {. inline . } =
2018-05-08 11:21:04 +00:00
## Convert an hex string to the corresponding unsigned integer
2018-10-08 18:17:39 +00:00
parse ( hexString , type result , radix = 16 )
2018-04-30 11:38:55 +00:00
2020-06-12 21:53:08 +00:00
# func toString*[bits: static[int]](num: StUint[bits], radix: static[uint8] = 10): string =
# ## Convert a Stint or Stuint to string.
# ## In case of negative numbers:
# ## - they are prefixed with "-" for base 10.
# ## - if not base 10, they are returned raw in two-complement form.
# static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
# const hexChars = "0123456789abcdef"
# const base = radix.uint8.stuint(bits)
# result = ""
# var (q, r) = divmod(num, base)
# while true:
# when bitsof(r.data) <= 64:
# result.add hexChars[r.data.int]
# else:
# result.add hexChars[r.truncate(int)]
# if q.isZero:
# break
# (q, r) = divmod(q, base)
# reverse(result)
# func toString*[bits: static[int]](num: Stint[bits], radix: static[int8] = 10): string =
# ## Convert a Stint or Stuint to string.
# ## In case of negative numbers:
# ## - they are prefixed with "-" for base 10.
# ## - if not base 10, they are returned raw in two-complement form.
# static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
# const hexChars = "0123456789abcdef"
# const base = radix.int8.stuint(bits)
# result = ""
# type T = Stuint[bits]
# let isNeg = num.isNegative
# let num = convert[T](if radix == 10 and isNeg: -num
# else: num)
# var (q, r) = divmod(num, base)
# while true:
# when bitsof(r.data) <= 64:
# result.add hexChars[r.data.int]
# else:
# result.add hexChars[r.truncate(int)]
# if q.isZero:
# break
# (q, r) = divmod(q, base)
# if isNeg and radix == 10:
# result.add '-'
# reverse(result)
# func `$`*(num: Stint or StUint): string {.inline.}=
# when num.data is SomeInteger:
# $num.data
# else:
# toString(num, 10)
# func toHex*[bits: static[int]](num: Stint[bits] or StUint[bits]): string {.inline.}=
# ## Convert to a hex string.
# ## Output is considered a big-endian base 16 string.
# ## Leading zeros are stripped. Use dumpHex instead if you need the in-memory representation
# toString(num, 16)
2020-06-13 14:44:13 +00:00
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 ( )
2020-06-12 21:53:08 +00:00
proc initFromBytesBE * [ bits : static [ int ] ] ( val : var Stuint [ bits ] ,
ba : openarray [ byte ] ,
allowPadding : static [ bool ] = true ) {. deprecated : " Use fromBytesBE instead " . } =
2018-07-04 15:34:14 +00:00
## Initializes a UInt[bits] value from a byte buffer storing a big-endian
## representation of a number.
##
## If `allowPadding` is set to false, the input array must be exactly
## (bits div 8) bytes long. Otherwise, it may be shorter and the remaining
## bytes will be assumed to be zero.
const N = bits div 8
when not allowPadding :
2019-03-14 02:43:51 +00:00
doAssert ( ba . len = = N )
2018-07-04 15:34:14 +00:00
else :
2019-03-14 02:43:51 +00:00
doAssert ba . len < = N
2019-10-16 11:37:02 +00:00
when system . cpuEndian = = bigEndian :
2018-07-04 15:34:14 +00:00
let baseIdx = N - val . len
else :
let baseIdx = ba . len - 1
2019-10-16 11:37:02 +00:00
when nimvm :
when system . cpuEndian = = bigEndian :
when allowPadding :
for i , b in ba : val . data . setByte ( baseIdx + i , b )
else :
for i , b in ba : val . data . setByte ( i , b )
else :
when allowPadding :
for i , b in ba : val . data . setByte ( baseIdx - i , b )
else :
for i , b in ba : val . data . setByte ( N - 1 - i , b )
else :
{. pragma : restrict , codegenDecl : " $# __restrict $# " . }
let r_ptr {. restrict . } = cast [ ptr array [ N , byte ] ] ( val . addr )
when system . cpuEndian = = bigEndian :
# TODO: due to https://github.com/status-im/nim-stint/issues/38
# We can't cast a stack byte array to stuint with a convenient proc signature.
when allowPadding :
for i , b in ba : r_ptr [ baseIdx + i ] = b
else :
for i , b in ba : r_ptr [ i ] = b
2018-07-04 15:34:14 +00:00
else :
2019-10-16 11:37:02 +00:00
when allowPadding :
for i , b in ba : r_ptr [ baseIdx - i ] = b
else :
for i , b in ba : r_ptr [ N - 1 - i ] = b
2018-07-04 15:34:14 +00:00
2022-02-24 19:09:53 +00:00
func significantBytesBE * ( val : openArray [ byte ] ) : int {. deprecated . } =
2018-07-04 15:34:14 +00:00
## Returns the number of significant trailing bytes in a big endian
## representation of a number.
2018-10-06 09:43:51 +00:00
# TODO: move that in https://github.com/status-im/nim-byteutils
2018-07-04 15:34:14 +00:00
for i in 0 .. < val . len :
if val [ i ] ! = 0 :
return val . len - i
return 1
2020-06-12 21:53:08 +00:00
func fromBytesBE * ( T : type Stuint , ba : openarray [ byte ] ,
allowPadding : static [ bool ] = true ) : T {. noInit , inline . } =
2018-07-04 15:34:14 +00:00
## This function provides a convenience wrapper around `initFromBytesBE`.
2020-06-12 21:53:08 +00:00
when not allowPadding :
{. deprecated : " fromBytesBE without padding is deprecated " . }
result . initFromBytesBE ( ba , allowPadding )
else :
result = endians2 . fromBytesBE ( T , ba )
2018-07-04 15:34:14 +00:00
2020-06-12 21:53:08 +00:00
func readUintBE * [ bits : static [ int ] ] ( ba : openarray [ byte ] ) : Stuint [ bits ] {. noInit , inline . } =
2018-05-08 11:21:04 +00:00
## Convert a big-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness)
## Input:
2022-02-24 19:09:53 +00:00
## - a big-endian openArray of size (bits div 8) at least
2018-05-08 11:21:04 +00:00
## Returns:
## - A unsigned integer of the same size with `bits` bits
##
2020-06-12 21:53:08 +00:00
## ⚠ If the openarray length is bigger than bits div 8, part converted is undefined behaviour.
result = endians2 . fromBytesBE ( Stuint [ bits ] , ba )
2018-05-08 11:21:04 +00:00
2020-06-12 21:53:08 +00:00
func toByteArrayBE * [ bits : static [ int ] ] ( n : StUint [ bits ] ) : array [ bits div 8 , byte ] {. noInit , inline . } =
2018-05-08 11:21:04 +00:00
## Convert a uint[bits] to to a big-endian array of bits div 8 bytes
## Input:
## - an unsigned integer
## Returns:
## - a big-endian array of the same size
2020-06-12 21:53:08 +00:00
result = n . toBytes ( bigEndian )
2019-12-18 13:02:16 +00:00
template hash * ( num : StUint | StInt ) : Hash =
# TODO:
# `hashData` is not particularly efficient.
# Explore better hashing solutions in nim-stew.
hashData ( unsafeAddr num , sizeof num )