Add bigint serialization (hex and decimal) (#29)

* Add serialization for decimal and hex

* Fix carry bug in signed add

* Add parsing test

* Improve highlow for int, remove most_significant_word_mut

* make conversion toString compile

* Add division corner case test

* Remove a buggy division shortcut

* Fix decimal string conversion

* Fix hex dumping

* Fix power of 2 division (what was I thinking?)

* Add hexdump test

* Move runtime check to compile-time

* Fix static assert check

* more compile-time asserts

* Fix parsing of negative hex numbers, add test_io to the suite

* dump default to bigEndian, split toString in Stint and Stuint

* Add (failing) tests with big ints conversion

* Temporarily remove all the noInit pragma
This commit is contained in:
Mamy Ratsimbazafy 2018-04-30 13:38:55 +02:00 committed by GitHub
parent f520f7817b
commit fd0482180f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 554 additions and 221 deletions

View File

@ -1,35 +0,0 @@
# 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.
# Utilities to debug MpInt
import
strutils,
../private/datatypes
func tohexBE*(x: UintImpl or IntImpl or SomeInteger): string =
## Stringify an uint to hex, Most significant byte on the left
## i.e.
## - 1.uint64 will be 00000001
## - (2.uint128)^64 + 1 will be 0000000100000001
const size = getSize(x) div 8
let bytes = cast[ptr array[size, byte]](x.unsafeaddr)
result = ""
when system.cpuEndian == littleEndian:
for i in countdown(size - 1, 0):
result.add toHex(bytes[i])
else:
for i in 0 ..< size:
result.add toHex(bytes[i])
func tohexBE*(x: Stint or StUint): string {.inline.}=
x.data.tohexBE

View File

@ -1,63 +0,0 @@
# 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.
import
./private/datatypes,
./private/int_negabs,
typetraits
func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}=
assert n >= 0.T
when result.data is UintImpl:
when getSize(n) > bits:
# To avoid a costly runtime check, we refuse storing into StUint types smaller
# than the input type.
raise newException(ValueError, "Input " & $n & " (" & $T &
") cannot be stored in a multi-precision " &
$bits & "-bit integer." &
"\nUse a smaller input type instead. This is a compile-time check" &
" to avoid a costly run-time bit_length check at each StUint initialization.")
else:
let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr)
when system.cpuEndian == littleEndian:
# "Least significant byte are at the beginning"
r_ptr[0] = n
else:
r_ptr[r_ptr[].len - 1] = n
else:
result.data = (type result.data)(n)
func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}=
when result.data is IntImpl:
when getSize(n) > bits:
# To avoid a costly runtime check, we refuse storing into StUint types smaller
# than the input type.
raise newException(ValueError, "Input " & $n & " (" & $T &
") cannot be stored in a multi-precision " &
$bits & "-bit integer." &
"\nUse a smaller input type instead. This is a compile-time check" &
" to avoid a costly run-time bit_length check at each StUint initialization.")
else:
let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr)
when system.cpuEndian == littleEndian:
# "Least significant byte are at the beginning"
if n < 0:
r_ptr[0] = -n
result = -result
else:
r_ptr[0] = n
else:
if n < 0:
r_ptr[r_ptr[].len - 1] = -n
result = -result
else:
r_ptr[r_ptr[].len - 1] = n
else:
result.data = (type result.data)(n)

View File

@ -6,7 +6,7 @@
# * 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.
{.pragma: fooPragma.}
import ./private/datatypes, macros
export StInt, IntImpl, intImpl # TODO remove the need to export intImpl and this macro
@ -15,14 +15,14 @@ type
Int256* = Stint[256]
template make_conv(conv_name: untyped, size: int): untyped =
func `convname`*(n: SomeInteger): Stint[size] {.inline, noInit.}=
func `convname`*(n: SomeInteger): Stint[size] {.inline, fooPragma.}=
n.stint(size)
make_conv(i128, 128)
make_conv(i256, 256)
template make_unary(op, ResultTy): untyped =
func `op`*(x: Stint): ResultTy {.noInit, inline.} =
func `op`*(x: Stint): ResultTy {.fooPragma, inline.} =
when ResultTy is Stint:
result.data = op(x.data)
else:
@ -30,7 +30,7 @@ template make_unary(op, ResultTy): untyped =
export op
template make_binary(op, ResultTy): untyped =
func `op`*(x, y: Stint): ResultTy {.noInit, inline.} =
func `op`*(x, y: Stint): ResultTy {.fooPragma, inline.} =
when ResultTy is Stint:
result.data = op(x.data, y.data)
else:
@ -60,7 +60,7 @@ import ./private/int_div
make_binary(`div`, Stint)
make_binary(`mod`, Stint)
func divmod*(x, y: Stint): tuple[quot, rem: Stint] {.noInit, inline.} =
func divmod*(x, y: Stint): tuple[quot, rem: Stint] {.fooPragma, inline.} =
(result.quot.data, result.rem.data) = divmod(x.data, y.data)
import ./private/int_comparison
@ -69,6 +69,7 @@ make_binary(`<`, bool)
make_binary(`<=`, bool)
make_binary(`==`, bool)
func isZero*(x: Stint): bool {.inline.} = isZero x.data
func isNegative*(x: Stint): bool {.inline.} = isNegative x.data
import ./private/int_bitwise_ops
@ -76,7 +77,7 @@ make_unary(`not`, Stint)
make_binary(`or`, Stint)
make_binary(`and`, Stint)
make_binary(`xor`, Stint)
# proc `shr`*(x: Stint, y: SomeInteger): Stint {.noInit, inline, noSideEffect.} =
# proc `shr`*(x: Stint, y: SomeInteger): Stint {.fooPragma, inline, noSideEffect.} =
# result.data = x.data shr y
# proc `shl`*(x: Stint, y: SomeInteger): Stint {.noInit, inline, noSideEffect.} =
# proc `shl`*(x: Stint, y: SomeInteger): Stint {.fooPragma, inline, noSideEffect.} =
# result.data = x.data shl y

283
src/io.nim Normal file
View File

@ -0,0 +1,283 @@
# 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.
{.pragma: fooPragma.}
import
./private/datatypes,
./private/int_negabs,
./private/initialization,
./private/[as_words, as_signed_words],
./int_public, ./uint_public,
typetraits, algorithm
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.
static: assert sizeof(T) * 8 <= bits, "Input type (" & $T &
") cannot be stored in a multi-precision " &
$bits & "-bit integer." &
"\nUse 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.}=
assert n >= 0.T
when result.data is UintImpl:
static_check_size(T, bits)
let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr)
when system.cpuEndian == littleEndian:
# "Least significant byte is at the beginning"
r_ptr[0] = n
else:
r_ptr[r_ptr[].len - 1] = n
else:
result.data = (type result.data)(n)
func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}=
when result.data is IntImpl:
static_check_size(T, bits)
let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr)
when system.cpuEndian == littleEndian:
# "Least significant byte is at the beginning"
if n < 0:
r_ptr[0] = -n
result = -result
else:
r_ptr[0] = n
else:
if n < 0:
r_ptr[r_ptr[].len - 1] = -n
result = -result
else:
r_ptr[r_ptr[].len - 1] = n
else:
result.data = (type result.data)(n)
func toInt*(num: Stint or StUint): int {.inline.}=
# Returns as int. Result is modulo 2^(sizeof(int)
num.data.least_significant_word.int
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")
func skipPrefixes(current_idx: var int, str: string, base: range[2..16]) {.inline.} =
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
assert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)"
if str[0] == '0':
if str[1] in {'x', 'X'}:
assert base == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)"
current_idx = 2
elif str[1] in {'o', 'O'}:
assert base == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)"
current_idx = 2
elif str[1] in {'b', 'B'}:
assert base == 2, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2)"
current_idx = 2
func nextNonBlank(current_idx: var int, s: string) {.inline.} =
## Move the current index, skipping white spaces and "_" characters.
const blanks = {' ', '_'}
inc current_idx
while s[current_idx] in blanks and current_idx < s.len:
inc current_idx
func readDecChar(c: range['0'..'9']): int {.inline.}=
## Converts a decimal char to an int
# specialization without branching for base <= 10.
ord(c) - ord('0')
func unsafeConv[bits: static[int]](n: range[0..16], T: typedesc[Stint[bits]|Stuint[bits]]): T =
## Fast convert a small int to a Stint/Stuint
## This assumes that the int always fit.
## Purpose:
## - Converting bases in the range [2..16]
## - Converting decimal/hexadecimal in range [0..15]
let r_ptr = cast[ptr array[bits div 8, byte]](result.addr)
when system.cpuEndian == littleEndian:
r_ptr[0] = n.byte
else:
r_ptr[r_ptr[].len - 1] = n.byte
func parse*[bits: static[int]](T: typedesc[Stuint[bits]], input: string, base: static[int]): T =
## Parse a string and store the result in a Stint[bits] or Stuint[bits].
static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported"
# 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
let radix = unsafeConv(base, T)
var curr = 0 # Current index in the string
skipPrefixes(curr, input, base)
while curr < input.len:
# TODO: overflow detection
when base <= 10:
result = result * radix + input[curr].readDecChar.unsafeConv(T)
else:
result = result * radix + input[curr].readHexChar.unsafeConv(T)
nextNonBlank(curr, input)
func parse*[bits: static[int]](T: typedesc[Stint[bits]], input: string, base: static[int]): T =
## Parse a string and store the result in a Stint[bits] or Stuint[bits].
static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported"
# 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
# For conversion we require overflowing operations (for example for negative hex numbers)
let radix = unsafeConv(base, Stuint[bits])
var
curr = 0 # Current index in the string
isNeg = false
no_overflow: Stuint[bits]
if input[curr] == '-':
assert base == 10, "Negative numbers are only supported with base 10 input."
isNeg = true
inc curr
else:
skipPrefixes(curr, input, base)
while curr < input.len:
# TODO: overflow detection
when base <= 10:
no_overflow = no_overflow * radix + input[curr].readDecChar.unsafeConv(Stuint[bits])
else:
no_overflow = no_overflow * radix + input[curr].readHexChar.unsafeConv(Stuint[bits])
nextNonBlank(curr, input)
# TODO: we can't create the lowest int this way
if isNeg:
result = -cast[Stint[bits]](no_overflow)
else:
result = cast[Stint[bits]](no_overflow)
func parse*[bits: static[int]](T: typedesc[Stint[bits]|Stuint[bits]], input: string): T {.inline, fooPragma.}=
## Parse a string and store the result in a Stint[bits] or Stuint[bits].
## Input is considered a decimal string.
# TODO: Have a default static argument in the previous proc. Currently we get
# "Cannot evaluate at compile-time" in several places (2018-04-26).
parse(T, input, 10)
func toString*[bits: static[int]](num: StUint[bits], base: static[int]): 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: assert (base >= 2) and base <= 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"
let radix = unsafeConv(base, type num)
result = ""
var (q, r) = divmod(num, radix)
while true:
result.add hexChars[r.toInt]
if q.isZero:
break
(q, r) = divmod(q, radix)
reverse(result)
func toString*[bits: static[int]](num: Stint[bits], base: static[int]): 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: assert (base >= 2) and base <= 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"
let radix = unsafeConv(base, type num)
result = ""
let isNeg = num.isNegative
let num = if base == 10 and isNeg: -num
else: num
var (q, r) = divmod(num, radix)
while true:
result.add hexChars[r.toInt]
if q.isZero:
break
(q, r) = divmod(q, radix)
if isNeg:
result.add '-'
reverse(result)
func toString*[bits: static[int]](num: Stint[bits] or StUint[bits]): string {.inline, fooPragma.}=
## Convert to a string.
## Output is considered a decimal string.
#
# TODO: Have a default static argument in the previous proc. Currently we get
# "Error: type mismatch: got <int, type StInt[128]>, required type static[int]"
toString(num, 10)
func dumpHex*(x: Stint or StUint, order: static[Endianness]): string =
## Stringify an int to hex.
## Note. Leading 0 are not removed. Use toString(n, base = 16) 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 = getSize(x.data) div 8
{.pragma: restrict, codegenDecl: "$# __restrict__ $#".}
let bytes {.restrict.}= cast[ptr array[size, byte]](x.unsafeaddr)
result = newString(2*size)
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*(x: Stint or StUint): string {.inline.}=
## Stringify an int to hex.
## By default, dump is done in bigEndian order.
dumpHex(x, bigEndian)
# TODO: Have a default static argument in the previous proc. Currently we get
# "Cannot evaluate at compile-time".

View File

@ -32,10 +32,11 @@ proc optimInt*(x: NimNode): NimNode =
error "Unreachable path reached"
proc isInt*(x: NimNode): static[bool] =
if eqIdent(x, "int64"): true
elif eqIdent(x, "int32"): true
elif eqIdent(x, "int16"): true
elif eqIdent(x, "int8"): true
if eqIdent(x, "uint64"): true
elif eqIdent(x, "int64"): true
elif eqIdent(x, "int32"): true
elif eqIdent(x, "int16"): true
elif eqIdent(x, "int8"): true
else: false
macro most_significant_word*(x: IntImpl): untyped =
@ -47,26 +48,28 @@ macro most_significant_word*(x: IntImpl): untyped =
else:
when system.cpuEndian == littleEndian:
let size = getSize(x)
let msw_pos = size - 1
let msw_pos = size div 64 - 1
else:
let msw_pos = 0
result = quote do:
# most significant word must be returned signed for addition/substraction
# overflow checking
cast[int](cast[`optim_type`](`x`)[`msw_pos`])
macro most_significant_word_mut*(x: var IntImpl): untyped =
macro least_significant_word*(x: IntImpl): untyped =
let optim_type = optimInt(x)
if optim_type.isInt:
result = quote do:
cast[var `optim_type`](`x`.addr)
cast[`optim_type`](`x`)
else:
when system.cpuEndian == littleEndian:
let size = getSize(x)
let msw_pos = size - 1
else:
let msw_pos = 0
else:
let msw_pos = size div 8 - 1
result = quote do:
(cast[ptr `optim_type`](`x`.unsafeAddr)[`msw_pos`])[]
cast[int](cast[`optim_type`](`x`)[`msw_pos`])
macro asSignedWordsZip*[T](
x, y: IntImpl[T],

View File

@ -60,6 +60,21 @@ proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNo
return rTree
result = inspect(ast)
macro least_significant_word*(x: UintImpl): untyped =
let optim_type = optimUInt(x)
if optim_type.isUInt:
result = quote do:
cast[`optim_type`](`x`)
else:
when system.cpuEndian == littleEndian:
let size = getSize(x)
let msw_pos = 0
else:
let msw_pos = size div 64 - 1
result = quote do:
cast[`optim_type`](`x`)[`msw_pos`]
macro asWords*(n: UintImpl or IntImpl, ignoreEndianness: static[bool], loopBody: untyped): untyped =
## Iterates over n, as an array of words.
## Input:

View File

@ -11,7 +11,6 @@
import macros
# The macro uintImpl must be exported
when defined(mpint_test):

View File

@ -6,14 +6,14 @@
# * 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.
{.pragma: fooPragma.}
import ./datatypes, ./conversion, ./as_signed_words
func `+`*(x, y: IntImpl): IntImpl {.noInit, inline.}=
func `+`*(x, y: IntImpl): IntImpl {.fooPragma, inline.}=
# Addition for multi-precision signed int.
type SubTy = type x.lo
result.lo = x.lo + y.lo
result.hi = (x.lo < y.lo).toSubtype(SubTy) + x.hi + y.hi
result.hi = (result.lo < y.lo).toSubtype(SubTy) + x.hi + y.hi
when compileOption("boundChecks"):
if unlikely(
@ -27,7 +27,7 @@ func `+=`*(x: var IntImpl, y: IntImpl) {.inline.}=
## In-place addition for multi-precision signed int.
x = x + y
func `-`*(x, y: IntImpl): IntImpl {.noInit, inline.}=
func `-`*(x, y: IntImpl): IntImpl {.fooPragma, inline.}=
# Substraction for multi-precision signed int.
type SubTy = type x.lo

View File

@ -6,26 +6,26 @@
# * 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.
{.pragma: fooPragma.}
import ./datatypes, ./as_words
func `not`*(x: IntImpl): IntImpl {.noInit, inline.}=
func `not`*(x: IntImpl): IntImpl {.fooPragma, inline.}=
## Bitwise complement of unsigned integer x
m_asWordsZip(result, x, ignoreEndianness = true):
result = not x
func `or`*(x, y: IntImpl): IntImpl {.noInit, inline.}=
func `or`*(x, y: IntImpl): IntImpl {.fooPragma, inline.}=
## `Bitwise or` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x or y
func `and`*(x, y: IntImpl): IntImpl {.noInit, inline.}=
func `and`*(x, y: IntImpl): IntImpl {.fooPragma, inline.}=
## `Bitwise and` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x and y
func `xor`*(x, y: IntImpl): IntImpl {.noInit, inline.}=
func `xor`*(x, y: IntImpl): IntImpl {.fooPragma, inline.}=
## `Bitwise xor` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x xor y

View File

@ -6,8 +6,8 @@
# * 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, ./int_negabs, ./uint_div
{.pragma: fooPragma.}
import ./datatypes, ./int_negabs, ./uint_div, ./int_comparison
# Here are the expected signs for division/modulo by opposite signs and both negative numbers
# in EVM
@ -32,11 +32,11 @@ import ./datatypes, ./int_negabs, ./uint_div
# echo "-10 mod -3: " & $(-10 mod -3) # -1
# echo '\n'
func divmod*(x, y: SomeSignedInt): tuple[quot, rem: SomeSignedInt] {.noInit, inline.}=
func divmod*(x, y: SomeSignedInt): tuple[quot, rem: SomeSignedInt] {.fooPragma, inline.}=
# hopefully the compiler fuse that in a single op
(x div y, x mod y)
proc divmod*[T](x, y: IntImpl[T]): tuple[quot, rem: IntImpl[T]] {.noInit.}=
proc divmod*[T](x, y: IntImpl[T]): tuple[quot, rem: IntImpl[T]] {.fooPragma.}=
## Divmod operation for multi-precision signed integer
result = cast[type result](divmod(

View File

@ -6,22 +6,20 @@
# * 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.
{.pragma: fooPragma.}
import ./datatypes, ./uint_bitwise_ops, ./int_bitwise_ops, ./initialization
import ./datatypes, ./as_signed_words
func low*(T: typedesc[IntImpl]): T {.inline.}=
# The lowest signed int has representation
# 0b1000_0000_0000_0000 ....
# so we only have to set the most significant bit.
type Msw = type result.most_significant_word
type U = Msw
most_significant_word_mut(result) = low(U)
func high*(T: typedesc[IntImpl]): T {.inline, noInit.}=
func high*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline, fooPragma.}=
# The lowest signed int has representation
# 0b0111_1111_1111_1111 ....
# so we only have to unset the most significant bit.
not low(T)
let only_msb_set = UintImpl[T].zero.not shr 1
result = cast[IntImpl[T]](only_msb_set)
func low*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline, fooPragma.}=
# The lowest signed int has representation
# 0b1000_0000_0000_0000 ....
# so we only have to set the most significant bit.
not high(IntImpl[T])

View File

@ -6,10 +6,10 @@
# * 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.
{.pragma: fooPragma.}
import ./datatypes, ./uint_mul
func `*`*[T](x, y: IntImpl[T]): IntImpl[T] {.inline, noInit.}=
func `*`*[T](x, y: IntImpl[T]): IntImpl[T] {.inline, fooPragma.}=
## Multiplication for multi-precision signed integers
# For 2-complement representation this is the exact same
# as unsigned multiplication. We don't need to deal with the sign

View File

@ -6,13 +6,13 @@
# * 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.
{.pragma: fooPragma.}
import
./datatypes,
./initialization, ./int_highlow,
./int_addsub
./int_addsub, ./int_comparison
func `-`*[T: IntImpl](x: T): T {.noInit, inline.}=
func `-`*[T: IntImpl](x: T): T {.fooPragma, inline.}=
# Negate a multi-precision signed int.
when compileOption("boundChecks"):
@ -22,7 +22,7 @@ func `-`*[T: IntImpl](x: T): T {.noInit, inline.}=
result = not x
result += one(T)
func abs*[T: IntImpl](x: T): T {.noInit, inline.}=
func abs*[T: IntImpl](x: T): T {.fooPragma, inline.}=
## Returns the absolute value of a signed int.
result = if x.isNegative: -x

View File

@ -6,8 +6,8 @@
# * 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 ./bithacks, ./conversion,
{.pragma: fooPragma.}
import ./bithacks, ./conversion, ./initialization,
./datatypes,
./uint_comparison,
./uint_bitwise_ops
@ -21,12 +21,12 @@ proc `+=`*(x: var UintImpl, y: UintImpl) {.noSideEffect, inline.}=
x.lo += y.lo
x.hi += (x.lo < y.lo).toSubtype(SubTy) + y.hi
proc `+`*(x, y: UintImpl): UintImpl {.noSideEffect, noInit, inline.}=
proc `+`*(x, y: UintImpl): UintImpl {.noSideEffect, fooPragma, inline.}=
# Addition for multi-precision unsigned int
result = x
result += y
proc `-`*(x, y: UintImpl): UintImpl {.noSideEffect, noInit, inline.}=
proc `-`*(x, y: UintImpl): UintImpl {.noSideEffect, fooPragma, inline.}=
# Substraction for multi-precision unsigned int
type SubTy = type x.lo
@ -36,3 +36,6 @@ proc `-`*(x, y: UintImpl): UintImpl {.noSideEffect, noInit, inline.}=
proc `-=`*(x: var UintImpl, y: UintImpl) {.noSideEffect, inline.}=
## In-place substraction for multi-precision unsigned int
x = x - y
func inc*(x: var UintImpl){.inline.}=
x += one(type x)

View File

@ -6,26 +6,26 @@
# * 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.
{.pragma: fooPragma.}
import ./datatypes, ./as_words
func `not`*(x: UintImpl): UintImpl {.noInit, inline.}=
func `not`*(x: UintImpl): UintImpl {.fooPragma, inline.}=
## Bitwise complement of unsigned integer x
m_asWordsZip(result, x, ignoreEndianness = true):
result = not x
func `or`*(x, y: UintImpl): UintImpl {.noInit, inline.}=
func `or`*(x, y: UintImpl): UintImpl {.fooPragma, inline.}=
## `Bitwise or` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x or y
func `and`*(x, y: UintImpl): UintImpl {.noInit, inline.}=
func `and`*(x, y: UintImpl): UintImpl {.fooPragma, inline.}=
## `Bitwise and` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x and y
func `xor`*(x, y: UintImpl): UintImpl {.noInit, inline.}=
func `xor`*(x, y: UintImpl): UintImpl {.fooPragma, inline.}=
## `Bitwise xor` of numbers x and y
m_asWordsZip(result, x, y, ignoreEndianness = true):
result = x xor y

View File

@ -6,7 +6,7 @@
# * 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.
{.pragma: fooPragma.}
import ./bithacks, ./conversion, ./initialization,
./datatypes,
./uint_comparison,
@ -48,7 +48,7 @@ func div2n1n[T: SomeunsignedInt](q, r: var T, n_hi, n_lo, d: T)
func div2n1n(q, r: var UintImpl, ah, al, b: UintImpl)
# Forward declaration
func divmod*(x, y: SomeUnsignedInt): tuple[quot, rem: SomeUnsignedInt] {.noInit, inline.}=
func divmod*(x, y: SomeUnsignedInt): tuple[quot, rem: SomeUnsignedInt] {.fooPragma, inline.}=
# hopefully the compiler fuse that in a single op
(x div y, x mod y)
@ -174,49 +174,34 @@ func divmodBZ[T](x, y: UintImpl[T], q, r: var UintImpl[T])=
if y.hi.isZero:
# Shortcut if divisor is smaller than half the size of the type
# Normalize
let
clz = countLeadingZeroBits(y.lo)
xx = x shl clz
yy = y.lo shl clz
if x.hi < y.lo:
# Normalize
let
clz = countLeadingZeroBits(y.lo)
xx = x shl clz
yy = y.lo shl clz
# If y is smaller than the base, normalizing x does not overflow.
# Compute directly
# Compute directly the low part
div2n1n(q.lo, r.lo, xx.hi, xx.lo, yy)
# Undo normalization
r.lo = r.lo shr clz
else:
# Normalizing x overflowed, we need to compute the high remainder first
(q.hi, r.hi) = divmod(x.hi, y.lo)
return
# Normalize the remainder. (x.lo is already normalized)
r.hi = r.hi shl clz
# General case
# Compute
div2n1n(q.lo, r.lo, r.hi, xx.lo, yy)
# Normalization
let clz = countLeadingZeroBits(y)
# Undo normalization
r.lo = r.lo shr clz
let
xx = UintImpl[type x](lo: x) shl clz
yy = y shl clz
# Given size n, dividing a 2n number by a 1n normalized number
# always gives a 1n remainder.
r.hi = zero(T)
# Compute
div2n1n(q, r, xx.hi, xx.lo, yy)
else: # General case
# Normalization
let clz = countLeadingZeroBits(y)
let
xx = UintImpl[type x](lo: x) shl clz
yy = y shl clz
# Compute
div2n1n(q, r, xx.hi, xx.lo, yy)
# Undo normalization
r = r shr clz
# Undo normalization
r = r shr clz
func divmodBS(x, y: UintImpl, q, r: var UintImpl) =
## Division for multi-precision unsigned uint
@ -266,8 +251,7 @@ func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]=
# It is a bit tricky with recursive types. An empty n.lo means 0 or sizeof(n.lo)
let y_ctz = getSize(y) - y_clz - 1
result.quot = x shr y_ctz
result.rem = y_ctz.initUintImpl(UintImpl[T])
result.rem = result.rem and x
result.rem = x and (y - one(type y))
elif x == y:
result.quot.lo = one(T)
elif x < y:

View File

@ -9,8 +9,8 @@
import ./datatypes, ./initialization
func low*(T: typedesc[UintImpl]): T {.inline, noInit.}=
func low*(T: typedesc[UintImpl]): T {.inline, fooPragma.}=
zero(T)
func high*(T: typedesc[UintImpl]): T {.inline, noInit.}=
func high*(T: typedesc[UintImpl]): T {.inline, fooPragma.}=
not zero(T)

View File

@ -6,8 +6,9 @@
# * 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 ./conversion,
{.pragma: fooPragma.}
import macros,
./conversion,
./initialization,
./datatypes,
./uint_comparison,
@ -53,23 +54,23 @@ template extPrecMulImpl(result: var UintImpl[uint64], op: untyped, u, v: uint64)
x0, x1, x2, x3: uint64
let
ul = u.lo
uh = u.hi
vl = v.lo
vh = v.hi
ul = lo(u)
uh = hi(u)
vl = lo(v)
vh = hi(v)
x0 = ul * vl
x1 = ul * vh
x2 = uh * vl
x3 = uh * vh
x1 += x0.hi # This can't carry
x1 += hi(x0) # This can't carry
x1 += x2 # but this can
if x1 < x2: # if carry, add it to x3
x3 += base
op(result.hi, x3 + x1.hi)
op(result.lo, (x1 shl p) or x0.lo)
op(result.hi, x3 + hi(x1))
op(result.lo, (x1 shl p) or lo(x0))
func extPrecMul*(result: var UintImpl[uint64], u, v: uint64) =
## Extended precision multiplication
@ -79,7 +80,15 @@ func extPrecAddMul(result: var UintImpl[uint64], u, v: uint64) =
## Extended precision fused in-place addition & multiplication
extPrecMulImpl(result, `+=`, u, v)
func extPrecMul*[T](result: var UintImpl[UintImpl[T]], x, y: UintImpl[T]) =
macro eqSym(x, y: untyped): untyped =
let eq = $x == $y # Unfortunately eqIdent compares to string.
result = quote do: `eq`
func extPrecAddMul[T](result: var UintImpl[UintImpl[T]], u, v: UintImpl[T])
func extPrecMul*[T](result: var UintImpl[UintImpl[T]], u, v: UintImpl[T])
# Forward declaration
template extPrecMulImpl*[T](result: var UintImpl[UintImpl[T]], op: untyped, x, y: UintImpl[T]) =
# See details at
# https://en.wikipedia.org/wiki/Karatsuba_algorithm
# https://locklessinc.com/articles/256bit_arithmetic/
@ -94,17 +103,20 @@ func extPrecMul*[T](result: var UintImpl[UintImpl[T]], x, y: UintImpl[T]) =
# and introduce branching
# - More total operations means more register moves
var z1: UintImpl[T]
var z1: type x
# Low part - z0
extPrecMul(result.lo, x.lo, y.lo)
when eqSym(op, `+=`):
extPrecAddMul(result.lo, x.lo, y.lo)
else:
extPrecMul(result.lo, x.lo, y.lo)
# Middle part - z1
extPrecMul(z1, x.hi, y.lo)
let carry_check = z1
extPrecAddMul(z1, x.lo, y.hi)
if z1 < carry_check:
result.hi.lo = one(T)
inc result.hi.lo
# High part - z2
result.hi.lo += z1.hi
@ -113,9 +125,17 @@ func extPrecMul*[T](result: var UintImpl[UintImpl[T]], x, y: UintImpl[T]) =
# Finalize low part
result.lo.hi += z1.lo
if result.lo.hi < z1.lo:
result.hi += one(UintImpl[T])
inc result.hi
func `*`*[T](x, y: UintImpl[T]): UintImpl[T] {.inline, noInit.}=
func extPrecAddMul[T](result: var UintImpl[UintImpl[T]], u, v: UintImpl[T]) =
## Extended precision fused in-place addition & multiplication
extPrecMulImpl(result, `+=`, u, v)
func extPrecMul*[T](result: var UintImpl[UintImpl[T]], u, v: UintImpl[T]) =
## Extended precision multiplication
extPrecMulImpl(result, `=`, u, v)
func `*`*[T](x, y: UintImpl[T]): UintImpl[T] {.inline, fooPragma.}=
## Multiplication for multi-precision unsigned uint
#
# For our representation, it is similar to school grade multiplication

View File

@ -7,5 +7,5 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./uint_public, ./int_public, ./init
export uint_public, int_public, init
import ./uint_public, ./int_public, ./io
export uint_public, int_public, io

View File

@ -9,20 +9,20 @@
import ./private/datatypes, macros
export StUint, UintImpl, uintImpl # TODO remove the need to export UintImpl and this macro
{.pragma: fooPragma.}
type
UInt128* = StUint[128]
UInt256* = StUint[256]
template make_conv(conv_name: untyped, size: int): untyped =
func `convname`*(n: SomeInteger): StUint[size] {.inline, noInit.}=
func `convname`*(n: SomeInteger): StUint[size] {.inline, fooPragma.}=
n.stuint(size)
make_conv(u128, 128)
make_conv(u256, 256)
template make_unary(op, ResultTy): untyped =
func `op`*(x: StUint): ResultTy {.noInit, inline.} =
func `op`*(x: StUint): ResultTy {.fooPragma, inline.} =
when ResultTy is StUint:
result.data = op(x.data)
else:
@ -30,7 +30,7 @@ template make_unary(op, ResultTy): untyped =
export op
template make_binary(op, ResultTy): untyped =
func `op`*(x, y: StUint): ResultTy {.noInit, inline.} =
func `op`*(x, y: StUint): ResultTy {.fooPragma, inline.} =
when ResultTy is StUint:
result.data = op(x.data, y.data)
else:
@ -56,7 +56,7 @@ import ./private/uint_div
make_binary(`div`, StUint)
make_binary(`mod`, StUint)
func divmod*(x, y: StUint): tuple[quot, rem: StUint] {.noInit, inline.} =
func divmod*(x, y: StUint): tuple[quot, rem: StUint] {.fooPragma, inline.} =
(result.quot.data, result.rem.data) = divmod(x.data, y.data)
import ./private/uint_comparison
@ -72,7 +72,7 @@ make_unary(`not`, StUint)
make_binary(`or`, StUint)
make_binary(`and`, StUint)
make_binary(`xor`, StUint)
proc `shr`*(x: StUint, y: SomeInteger): StUint {.noInit, inline, noSideEffect.} =
proc `shr`*(x: StUint, y: SomeInteger): StUint {.fooPragma, inline, noSideEffect.} =
result.data = x.data shr y
proc `shl`*(x: StUint, y: SomeInteger): StUint {.noInit, inline, noSideEffect.} =
proc `shl`*(x: StUint, y: SomeInteger): StUint {.fooPragma, inline, noSideEffect.} =
result.data = x.data shl y

View File

@ -16,3 +16,5 @@ import test_uint_endianness,
import test_int_endianness,
test_int_comparison,
test_int_addsub
import test_io

View File

@ -107,3 +107,15 @@ suite "Testing signed int division and modulo implementation":
# check: q.hi == 0'u64
# check: r.lo == 1'u64
# check: r.hi == 0'u64
test "Divmod(1234567891234567890, 10) returns the correct result":
let a = cast[Stint[64]](1234567891234567890'i64)
let b = cast[Stint[64]](10'i64)
let qr = divmod(a, b)
let q = cast[int64](qr.quot)
let r = cast[int64](qr.rem)
check: q == 123456789123456789'i64
check: r == 0'i64

99
tests/test_io.nim Normal file
View File

@ -0,0 +1,99 @@
# 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.
import ../src/stint, unittest, strutils
suite "Testing input and output procedures":
test "Creation from decimal strings":
block:
let a = parse(Stint[64], "123456789")
let b = 123456789.stint(64)
check: a == b
check: 123456789'i64 == cast[int64](a)
block:
let a = parse(Stuint[64], "123456789")
let b = 123456789.stuint(64)
check: a == b
check: 123456789'u64 == cast[uint64](a)
block:
let a = parse(Stint[64], "-123456789")
let b = (-123456789).stint(64)
check: a == b
check: -123456789'i64 == cast[int64](a)
test "Creation from hex strings":
block:
let a = parse(Stint[64], "0xFF", 16)
let b = 255.stint(64)
check: a == b
check: 255'i64 == cast[int64](a)
block:
let a = parse(Stuint[64], "0xFF", 16)
let b = 255.stuint(64)
check: a == b
check: 255'u64 == cast[uint64](a)
block:
let a = parse(Stint[16], "0xFFFF", 16)
let b = (-1'i16).stint(16)
check: a == b
check: -1'i16 == cast[int16](a)
test "Conversion to decimal strings":
block:
let a = 1234567891234567890.stint(128)
check: a.toString == "1234567891234567890"
block:
let a = 1234567891234567890.stuint(128)
check: a.toString == "1234567891234567890"
block:
let a = (-1234567891234567890).stint(128)
check: a.toString == "-1234567891234567890"
test "Conversion to hex strings":
block:
let a = 0x1234567890ABCDEF.stint(128)
check: a.toString(base = 16).toUpperAscii == "1234567890ABCDEF"
block:
let a = 0x1234567890ABCDEF.stuint(128)
check: a.toString(base = 16).toUpperAscii == "1234567890ABCDEF"
# TODO: negative hex
test "Hex dump":
block:
let a = 0x1234'i32.stint(32)
check: a.dumpHex(bigEndian).toUpperAscii == "00001234"
block:
let a = 0x1234'i32.stint(32)
check: a.dumpHex(littleEndian).toUpperAscii == "34120000"
test "Back and forth bigint conversion consistency":
block:
let s = "1234567890123456789012345678901234567890123456789"
let a = parse(StInt[512], s)
check: a.toString == s
block:
let s = "1234567890123456789012345678901234567890123456789"
let a = parse(StUInt[512], s)
check: a.toString == s

View File

@ -56,3 +56,15 @@ suite "Testing unsigned int division and modulo implementation":
check: q.hi == 0'u64
check: r.lo == 1'u64
check: r.hi == 0'u64
test "Divmod(1234567891234567890, 10) returns the correct result":
let a = cast[StUint[64]](1234567891234567890'u64)
let b = cast[StUint[64]](10'u64)
let qr = divmod(a, b)
let q = cast[uint64](qr.quot)
let r = cast[uint64](qr.rem)
check: q == 123456789123456789'u64
check: r == 0'u64