refactoring away for loop macros

* remove experimental for loop macro usage
* make implementation of several operations follow data structure
(recursive data -> recursive implementation)
* rename getSize -> bitsof to avoid bits vs bytes confusion
* fix potential 32-bit issue where asSignedWords cast to `int` even when
`uint64` was used as storage
* `hi` for signed ints now is signed - this replaces `asSignedWords` and
makes several int operations more natural
* fix bit size assert
This commit is contained in:
Jacek Sieka 2018-10-25 12:58:40 +02:00
parent 4fe901d33b
commit 9027fbea3e
No known key found for this signature in database
GPG Key ID: 6299FEB3EB6FA465
17 changed files with 179 additions and 465 deletions

View File

@ -10,7 +10,6 @@
import
./private/datatypes,
./private/int_negabs,
./private/as_words,
./int_public, ./uint_public,
typetraits, algorithm
@ -24,22 +23,11 @@ template static_check_size(T: typedesc[SomeInteger], bits: static[int]) =
"\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."
template assign_leastSignificantWords[T: SomeInteger](result: var (Stuint|Stint), n: T) =
template lsw_result: untyped = leastSignificantWord(result.data)
template slsw_result: untyped = secondLeastSignificantWord(result.data)
const wordSize = lsw_result.getSize
when sizeof(T) * 8 <= wordSize:
lsw_result = (type lsw_result)(n)
else: # We try to store an int64 in 2 x uint32 or 4 x uint16
# For now we only support assignation from 64 to 2x32 bit
const
size = getSize(T)
halfSize = size div 2
halfMask = (1.T shl halfSize) - 1.T
lsw_result = (type lsw_result)(n and halfMask)
slsw_result = (type slsw_result)(n shr halfSize)
func assignLo(result: var (UintImpl | IntImpl), n: SomeInteger) {.inline.} =
when result.lo is UintImpl:
assignLo(result.lo, n)
else:
result.lo = (type result.lo)(n)
func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}=
## Converts an integer to an arbitrary precision integer.
@ -47,7 +35,7 @@ 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)
assign_leastSignificantWords(result, n)
assignLo(result.data, n)
else:
result.data = (type result.data)(n)
@ -58,12 +46,12 @@ func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}=
static_check_size(T, bits)
when T is SomeSignedInt:
if n < 0:
assign_leastSignificantWords(result, -n)
assignLo(result.data, -n)
result = -result
else:
assign_leastSignificantWords(result, n)
assignLo(result.data, n)
else:
assign_leastSignificantWords(result, n)
assignLo(result.data, n)
else:
result.data = (type result.data)(n)
@ -78,14 +66,7 @@ func truncate*(num: Stint or StUint, T: typedesc[int or uint or int64 or uint64]
## 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.
when T is int: cast[int](num.data.leastSignificantWord)
elif T is uint: uint num.data.leastSignificantWord
elif T is int64:
when sizeof(uint) == 8: cast[int64](num.data.leastSignificantWord)
else: cast[int64](num.data.leastSignificantTwoWords)
elif T is uint64:
when sizeof(uint) == 8: uint64 num.data.leastSignificantWord
else: cast[uint64](num.data.leastSignificantTwoWords)
cast[T](num.data.leastSignificantWord)
func toInt*(num: Stint or StUint): int {.inline, deprecated:"Use num.truncate(int) instead".}=
num.truncate(int)
@ -280,7 +261,7 @@ func dumpHex*(x: Stint or StUint, order: static[Endianness] = bigEndian): string
const
hexChars = "0123456789abcdef"
size = getSize(x.data) div 8
size = bitsof(x.data) div 8
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
let bytes {.restrict.}= cast[ptr array[size, byte]](x.unsafeaddr)

View File

@ -1,182 +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 ./datatypes, ./conversion, macros
# #########################################################################
# Multi-precision ints to compile-time array of words
proc asWordsImpl(x: NimNode, current_path: NimNode, result: var NimNode) =
## Transforms an UintImpl/IntImpl into an array of words
## at compile-time. Recursive implementation.
## Result is from most significant word to least significant
let node = x.getTypeInst
if node.kind == nnkBracketExpr:
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl")
let hi = nnkDotExpr.newTree(current_path, newIdentNode("hi"))
let lo = nnkDotExpr.newTree(current_path, newIdentNode("lo"))
asWordsImpl(node[1], hi, result)
asWordsImpl(node[1], lo, result)
else:
result.add current_path
# #########################################################################
# Accessors
macro asWords(x: UintImpl or IntImpl, idx: static[int]): untyped =
## Access a single element from a multiprecision ints
## as if if was stored as an array
## x.asWords[0] is the most significant word
var words = nnkBracket.newTree()
asWordsImpl(x, x, words)
result = words[idx]
macro most_significant_word*(x: UintImpl or IntImpl): untyped =
result = getAST(asWords(x, 0))
macro leastSignificantWord*(x: UintImpl or IntImpl): untyped =
var words = nnkBracket.newTree()
asWordsImpl(x, x, words)
result = words[words.len - 1]
macro secondLeastSignificantWord*(x: UintImpl or IntImpl): untyped =
var words = nnkBracket.newTree()
asWordsImpl(x, x, words)
result = words[words.len - 2]
macro leastSignificantTwoWords*(x: UintImpl or IntImpl): untyped =
var words = nnkBracket.newTree()
asWordsImpl(x, x, words)
when system.cpuEndian == bigEndian:
result = nnkBracket.newTree(words[words.len - 2], words[words.len - 1])
else:
result = nnkBracket.newTree(words[words.len - 1], words[words.len - 2])
# #########################################################################
# Iteration macros
proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNode =
# Args:
# - The full syntax tree
# - an array of replacement value
# - an array of identifiers to replace
proc inspect(node: NimNode): NimNode =
case node.kind:
of {nnkIdent, nnkSym}:
for i, c in to_replace:
if node.eqIdent($c):
return replacing[i]
return node
of nnkEmpty:
return node
of nnkLiterals:
return node
else:
var rTree = node.kind.newTree()
for child in node:
rTree.add inspect(child)
return rTree
result = inspect(ast)
macro asWordsIterate(wordsIdents: untyped, sid0, sid1, sid2: typed, signed: static[bool], loopBody: untyped): untyped =
# TODO: We can't use varargs[typed] without losing type info - https://github.com/nim-lang/Nim/issues/7737
# So we need a workaround we accept fixed 3 args sid0, sid1, sid2 and will just ignore what is not used
result = newStmtList()
let NbStints = wordsIdents.len
# 1. Get the words of each stint
# + Workaround varargs[typed] losing type info https://github.com/nim-lang/Nim/issues/7737
var words = nnkBracket.newTree
block:
var wordList = nnkBracket.newTree
asWordsImpl(sid0, sid0, wordList)
words.add wordList
if NbStints > 1:
var wordList = nnkBracket.newTree
asWordsImpl(sid1, sid1, wordList)
words.add wordList
if NbStints > 2:
var wordList = nnkBracket.newTree
asWordsImpl(sid2, sid2, wordList)
words.add wordList
# 2. Construct an unrolled loop
# We replace each occurence of each words
# in the original loop by how to access it.
let NbWords = words[0].len
for currDepth in 0 ..< NbWords:
var replacing = nnkBracket.newTree
for currStint in 0 ..< NbStints:
var w = words[currStint][currDepth]
if currDepth == 0 and signed:
w = quote do: toInt(`w`)
replacing.add w
let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents)
result.add quote do:
block: `body`
template asWordsParse(): untyped {.dirty.}=
# ##### Tree representation
# for word_a, word_b in asWords(a, b):
# discard
# ForStmt
# Ident "word_a"
# Ident "word_b"
# Call
# Ident "asWords"
# Ident "a"
# Ident "b"
# StmtList
# DiscardStmt
# Empty
# 1. Get the words variable idents
var wordsIdents = nnkBracket.newTree
var idx = 0
while x[idx].kind == nnkIdent:
wordsIdents.add x[idx]
inc idx
# 2. Get the multiprecision ints idents
var stintsIdents = nnkArgList.newTree # nnkArgList allows to keep the type when passing to varargs[typed]
# but varargs[typed] has further issues ¯\_(ツ)_/¯
idx = 1
while idx < x[wordsIdents.len].len and x[wordsIdents.len][idx].kind == nnkIdent:
stintsIdents.add x[wordsIdents.len][idx]
inc idx
assert wordsIdents.len == stintsIdents.len, "The number of loop variables and multiprecision integers t iterate on must be the same"
# 3. Get the body and pass the bucket to a typed macro
# + unroll varargs[typed] manually as workaround for https://github.com/nim-lang/Nim/issues/7737
var body = x[x.len - 1]
let sid0 = stintsIdents[0]
let sid1 = if stintsIdents.len > 1: stintsIdents[1] else: newEmptyNode()
let sid2 = if stintsIdents.len > 2: stintsIdents[2] else: newEmptyNode()
macro asWords*(x: ForLoopStmt): untyped =
## This unrolls the body of the for loop and applies it for each word.
## Words are processed from most significant word to least significant.
asWordsParse()
result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, false, `body`)
macro asSignedWords*(x: ForLoopStmt): untyped =
## This unrolls the body of the for loop and applies it for each word.
## Words are processed from most significant word to least significant.
## The most significant word is returned signed for proper comparison.
asWordsParse()
result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, true, `body`)

View File

@ -7,7 +7,7 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./conversion, stdlib_bitops, as_words
import ./datatypes, ./conversion, stdlib_bitops
export stdlib_bitops
# We reuse bitops from Nim standard lib, and expand it for multi-precision int.
@ -17,19 +17,10 @@ export stdlib_bitops
func countLeadingZeroBits*(n: UintImpl): int {.inline.} =
## Returns the number of leading zero bits in integer.
const maxHalfRepr = getSize(n) div 2
const maxHalfRepr = bitsof(n) div 2
let hi_clz = n.hi.countLeadingZeroBits
result = if hi_clz == maxHalfRepr:
n.lo.countLeadingZeroBits + maxHalfRepr
else: hi_clz
func isMsbSet*[T: SomeInteger](n: T): bool {.inline.}=
## Returns the most significant bit of an integer.
const msb_pos = sizeof(T) * 8 - 1
result = bool(n.toUint shr msb_pos)
func isMsbSet*(n: UintImpl or IntImpl): bool {.inline.}=
## Returns the most significant bit of an arbitrary precision integer.
result = isMsbSet most_significant_word(n)

View File

@ -12,11 +12,11 @@ import ./datatypes
func toSubtype*[T: SomeInteger](b: bool, _: typedesc[T]): T {.inline.}=
b.T
func toSubtype*[T: UintImpl](b: bool, _: typedesc[T]): T {.inline.}=
func toSubtype*[T: UintImpl | IntImpl](b: bool, _: typedesc[T]): T {.inline.}=
type SubTy = type result.lo
result.lo = toSubtype(b, SubTy)
func toUint*(n: UintImpl or IntImpl): auto {.inline.}=
func toUint*(n: UintImpl or IntImpl or SomeSignedInt): auto {.inline.}=
## Casts an unsigned integer to an uint of the same size
# TODO: uint128 support
when n.sizeof > 8:
@ -34,7 +34,7 @@ func toUint*(n: SomeUnsignedInt): SomeUnsignedInt {.inline.}=
## No-op overload of multi-precision int casting
n
func asDoubleUint*(n: BaseUint): auto {.inline.} =
func asDoubleUint*(n: UintImpl | SomeUnsignedInt): auto {.inline.} =
## Convert an integer or StUint to an uint with double the size
type Double = (
when n.sizeof == 4: uint64

View File

@ -9,9 +9,6 @@
# TODO: test if GCC/Clang support uint128 natively
import macros
# The macro uintImpl must be exported
# #### Overview
#
# Stint extends the default uint8, uint16, uint32, uint64 with power of 2 integers.
@ -52,7 +49,7 @@ import macros
# - a root + intermediate nodes + leaves (uint256)
# should be enough to ensure they work at all sizes, edge cases included.
# - Adding a new backend like uint128 (GCC/Clang) or uint256 (LLVM instrinsics only) is just adding
# a new case in the `uintImpl` macro.
# a new case in the `uintImpl` template.
# - All math implementations of the operations have a straightforward translation
# to a high-low structure, including the fastest Karatsuba multiplication
# and co-recursive division algorithm by Burnikel and Ziegler.
@ -94,127 +91,70 @@ import macros
# If you have a lot of computations and intermediate variables it's probably worthwhile
# to explore creating an object pool to reuse the memory buffers.
when not defined(stint_test):
macro uintImpl*(bits: static[int]): untyped =
# Release version, word size is uint64 (even on 32-bit arch).
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
assert bits >= 8, "The number of bits in a should be greater or equal to 8"
template checkDiv2(bits: static[int]): untyped =
static:
doAssert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
doAssert bits >= 8, "The number of bits in a should be greater or equal to 8"
bits div 2
if bits >= 128:
let inner = getAST(uintImpl(bits div 2))
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
elif bits == 64:
result = ident("uint64")
elif bits == 32:
result = ident("uint32")
elif bits == 16:
result = ident("uint16")
elif bits == 8:
result = ident("uint8")
else:
error "Fatal: unreachable"
when defined(mpint_test):
template uintImpl*(bits: static[int]): untyped =
# Test version, StUint[64] = 2 uint32. Test the logic of the library
macro intImpl*(bits: static[int]): untyped =
# Release version, word size is uint64 (even on 32-bit arch).
# Note that int of size 128+ are implemented in terms of unsigned ints
# Signed operations are built on top of that.
when bits >= 128: UintImpl[uintImpl(checkDiv2(bits))]
elif bits == 64: UintImpl[uint32]
elif bits == 32: UintImpl[uint16]
elif bits == 16: UintImpl[uint8]
else: {.fatal: "Only power-of-2 >=16 supported when testing" .}
template intImpl*(bits: static[int]): untyped =
# Test version, StInt[64] = 2 uint32. Test the logic of the library
# int is implemented using a signed hi part and an unsigned lo part, given
# that the sign resides in hi
when bits >= 128: IntImpl[intImpl(checkDiv2(bits)), uintImpl(checkDiv2(bits))]
elif bits == 64: IntImpl[int32, uint32]
elif bits == 32: IntImpl[int16, uint16]
elif bits == 16: IntImpl[int8, uint8]
else: {.fatal: "Only power-of-2 >=16 supported when testing" .}
if bits >= 128:
let inner = getAST(uintImpl(bits div 2))
result = newTree(nnkBracketExpr, ident("IntImpl"), inner)
elif bits == 64:
result = ident("int64")
elif bits == 32:
result = ident("int32")
elif bits == 16:
result = ident("int16")
elif bits == 8:
result = ident("int8")
else:
error "Fatal: unreachable"
else:
macro uintImpl*(bits: static[int]): untyped =
# Test version, word size is uint32. Test the logic of the library.
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
assert bits >= 16, "The number of bits in a should be greater or equal to 16"
template uintImpl*(bits: static[int]): untyped =
when bits >= 128: UintImpl[uintImpl(checkDiv2(bits))]
elif bits == 64: uint64
elif bits == 32: uint32
elif bits == 16: uint16
elif bits == 8: uint8
else: {.fatal: "Only power-of-2 >=8 supported" .}
if bits >= 128:
let inner = getAST(uintImpl(bits div 2))
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
elif bits == 64:
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint32"))
elif bits == 32:
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint16"))
elif bits == 16:
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint8"))
else:
error "Fatal: unreachable"
template intImpl*(bits: static[int]): untyped =
# int is implemented using a signed hi part and an unsigned lo part, given
# that the sign resides in hi
macro intImpl*(bits: static[int]): untyped =
# Test version, word size is uint32. Test the logic of the library.
# Note that ints are implemented in terms of unsigned ints
# Signed operations will be built on top of that.
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
assert bits >= 16, "The number of bits in a should be greater or equal to 16"
if bits >= 128:
let inner = getAST(uintImpl(bits div 2)) # IntImpl is built on top of UintImpl
result = newTree(nnkBracketExpr, ident("IntImpl"), inner)
elif bits == 64:
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint32"))
elif bits == 32:
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint16"))
elif bits == 16:
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint8"))
else:
error "Fatal: unreachable"
proc getSize*(x: NimNode): static[int] =
# Default Nim's `sizeof` doesn't always work at compile-time, pending PR https://github.com/nim-lang/Nim/pull/5664
var multiplier = 1
var node = x.getTypeInst
while node.kind == nnkBracketExpr:
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl"), (
"getSize only supports primitive integers, Stint and Stuint")
multiplier *= 2
node = node[1]
# node[1] has the type
# size(node[1]) * multiplier is the size in byte
# For optimization we cast to the biggest possible uint
result = if eqIdent(node, "uint64") or eqIdent(node, "int64"): multiplier * 64
elif eqIdent(node, "uint32") or eqIdent(node, "int32"): multiplier * 32
elif eqIdent(node, "uint16") or eqIdent(node, "int16"): multiplier * 16
elif eqIdent(node, "uint8") or eqIdent(node, "int8"): multiplier * 8
elif eqIdent(node, "int") or eqIdent(node, "uint"):
multiplier * 8 * sizeof(int)
else:
assert false, "Error when computing the size. Found: " & $node
0
macro getSize*(x: typed): untyped =
let size = getSize(x)
result = quote do:
`size`
when bits >= 128: IntImpl[intImpl(checkDiv2(bits)), uintImpl(checkDiv2(bits))]
elif bits == 64: int64
elif bits == 32: int32
elif bits == 16: int16
elif bits == 8: int8
else: {.fatal: "Only power-of-2 >=8 supported" .}
type
# ### Private ### #
BaseUint* = UintImpl or SomeUnsignedInt
UintImpl*[Baseuint] = object
UintImpl*[BaseUint] = object
when system.cpuEndian == littleEndian:
lo*, hi*: BaseUint
else:
hi*, lo*: BaseUint
IntImpl*[Baseuint] = object
IntImpl*[BaseInt, BaseUint] = object
# Ints are implemented in terms of uints
when system.cpuEndian == littleEndian:
lo*, hi*: BaseUint
lo*: BaseUint
hi*: BaseInt
else:
hi*, lo*: BaseUint
hi*: BaseInt
lo*: BaseUint
# ### Private ### #
StUint*[bits: static[int]] = object
@ -222,3 +162,42 @@ type
StInt*[bits: static[int]] = object
data*: intImpl(bits)
template bitsof*(x: SomeInteger): int = sizeof(x) * 8
# XXX: https://github.com/nim-lang/Nim/issues/9494 - the extra overloads
# can eventually be collapsed to one..
template bitsof*(x: UintImpl[SomeInteger]): untyped =
2 * bitsof(x.lo)
template bitsof*(x: UintImpl[UintImpl[SomeInteger]]): untyped =
2 * bitsof(x.lo)
template bitsof*(x: UintImpl[UintImpl[UintImpl[SomeInteger]]]): untyped =
2 * bitsof(x.lo)
template bitsof*(x: UintImpl[UintImpl[UintImpl[UintImpl[SomeInteger]]]]): untyped =
2 * bitsof(x.lo)
template applyHiLo*(a: UintImpl | IntImpl, c: untyped): untyped =
## Apply `c` to each of `hi` and `lo`
var res: type a
res.hi = c(a.hi)
res.lo = c(a.lo)
res
template applyHiLo*(a, b: UintImpl | IntImpl, c: untyped): untyped =
## Apply `c` to each of `hi` and `lo`
var res: type a
res.hi = c(a.hi, b.hi)
res.lo = c(a.lo, b.lo)
res
func leastSignificantWord*(num: UintImpl | IntImpl): auto {.inline.} =
when num.lo is UintImpl:
num.lo.leastSignificantWord
else:
num.lo
func mostSignificantWord*(num: UintImpl | IntImpl): auto {.inline.} =
when num.hi is (UintImpl | IntImpl):
num.hi.mostSignificantWord
else:
num.hi

View File

@ -7,13 +7,13 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./as_words, typetraits
import ./datatypes
func zero*(T: typedesc): T {.inline.} =
discard
func one*(T: typedesc[UintImpl or IntImpl]): T {.inline.} =
leastSignificantWord(result) = 1
func one*(T: typedesc[SomeInteger]): T {.inline.} =
1
func one*(T: typedesc[UintImpl or IntImpl]): T {.inline.} =
result.lo = one(type result.lo)

View File

@ -13,7 +13,7 @@ import
func `+`*(x, y: IntImpl): IntImpl {.inline.}=
# Addition for multi-precision signed int.
type SubTy = type x.lo
type SubTy = type x.hi
result.lo = x.lo + y.lo
result.hi = (result.lo < y.lo).toSubtype(SubTy) + x.hi + y.hi
@ -32,7 +32,7 @@ func `+=`*(x: var IntImpl, y: IntImpl) {.inline.}=
func `-`*(x, y: IntImpl): IntImpl {.inline.}=
# Substraction for multi-precision signed int.
type SubTy = type x.lo
type SubTy = type x.hi
result.lo = x.lo - y.lo
result.hi = x.hi - y.hi - (x.lo < y.lo).toSubtype(SubTy)

View File

@ -7,32 +7,20 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./as_words
import ./datatypes, ./uint_bitwise_ops
func `not`*(x: IntImpl): IntImpl {.inline.}=
## Bitwise complement of unsigned integer x
{.push experimental: "forLoopMacros".}
for wr, wx in asWords(result, x):
wr = not wx
{.pop.}
applyHiLo(x, `not`)
func `or`*(x, y: IntImpl): IntImpl {.inline.}=
## `Bitwise or` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx or wy
{.pop.}
applyHiLo(x, y, `or`)
func `and`*(x, y: IntImpl): IntImpl {.inline.}=
## `Bitwise and` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx and wy
{.pop.}
applyHiLo(x, y, `and`)
func `xor`*(x, y: IntImpl): IntImpl {.inline.}=
## `Bitwise xor` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx xor wy
{.pop.}
applyHiLo(x, y, `xor`)

View File

@ -7,53 +7,37 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./bithacks, ./as_words,
./bithacks
import ./datatypes, ./bithacks, ./uint_comparison
func isZero*(n: SomeSignedInt): bool {.inline.} =
n == 0
func isZero*(n: IntImpl): bool {.inline.} =
{.push experimental: "forLoopMacros".}
for word in asWords(n):
if word != 0:
return false
{.pop.}
return true
n.hi.isZero and n.lo.isZero
func isNegative*(n: SomeSignedInt): bool {.inline.} =
n < 0
func isNegative*(n: IntImpl): bool {.inline.} =
## Returns true if a number is negative:
n.isMsbSet
n.hi.isNegative
func `<`*(x, y: IntImpl): bool {.inline.}=
# Lower comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asSignedWords(x, y):
if wx != wy:
return wx < wy
{.pop.}
return false # they're equal
x.hi < y.hi or
(x.hi == y.hi and x.lo < y.lo)
func `==`*(x, y: IntImpl): bool {.inline.}=
# Equal comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asWords(x, y):
if wx != wy:
return false
{.pop.}
return true # they're equal
x.hi == y.hi and x.lo == y.lo
func `<=`*(x, y: IntImpl): bool {.inline.}=
# Lower or equal comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asSignedWords(x, y):
if wx != wy:
return wx < wy
{.pop.}
return true # they're equal
func isOdd*(x: IntImpl): bool {.inline.}=
bool(x.leastSignificantWord and 1)
x.hi < y.hi or
(x.hi == y.hi and x.lo <= y.lo)
func isEven*(x: IntImpl): bool {.inline.}=
not x.isOdd
x.lo.isEven
func isOdd*(x: IntImpl): bool {.inline.}=
not x.isEven

View File

@ -36,12 +36,12 @@ func divmod*(x, y: SomeSignedInt): tuple[quot, rem: SomeSignedInt] {.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]] =
proc divmod*[T, T2](x, y: IntImpl[T, T2]): tuple[quot, rem: IntImpl[T, T2]] =
## Divmod operation for multi-precision signed integer
result = cast[type result](divmod(
cast[UintImpl[T]](x.abs),
cast[UintImpl[T]](y.abs)
cast[UintImpl[T2]](x.abs),
cast[UintImpl[T2]](y.abs)
))
if (x.isNegative xor y.isNegative):

View File

@ -7,17 +7,21 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./int_bitwise_ops, ./as_words, ./initialization
import ./datatypes, ./int_bitwise_ops, ./initialization, ./uint_highlow
func high*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline.}=
# XXX There's some Araq reason why this isn't part of the std lib..
func high(_: typedesc[SomeUnsignedInt]): SomeUnsignedInt =
not SomeUnsignedInt(0'u8)
func high*[T, T2](_: typedesc[IntImpl[T, T2]]): IntImpl[T, T2] {.inline.}=
# The highest signed int has representation
# 0b0111_1111_1111_1111 ....
# so we only have to unset the most significant bit.
result = not result
most_significant_word(result) = most_significant_word(result) shr 1
result.hi = high(type result.hi)
result.lo = high(type result.lo)
func low*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline.}=
func low*[T, T2](_: typedesc[IntImpl[T, T2]]): IntImpl[T, T2] {.inline.}=
# The lowest signed int has representation
# 0b1000_0000_0000_0000 ....
# so we only have to set the most significant bit.
not high(IntImpl[T])
not high(IntImpl[T, T2])

View File

@ -9,9 +9,9 @@
import ./datatypes, ./uint_mul
func `*`*[T](x, y: IntImpl[T]): IntImpl[T] {.inline.}=
func `*`*[T, T2](x, y: IntImpl[T, T2]): IntImpl[T, T2] {.inline.}=
## 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
# TODO: overflow detection.
cast[type result](cast[UIntImpl[T]](x) * cast[UIntImpl[T]](y))
cast[type result](cast[UIntImpl[T2]](x) * cast[UIntImpl[T2]](y))

View File

@ -12,15 +12,15 @@ import
./initialization, ./int_highlow,
./int_addsub, ./int_comparison
func `-`*[T: IntImpl](x: T): T {.inline.}=
func `-`*(x: IntImpl): IntImpl {.inline.}=
# Negate a multi-precision signed int.
when compileOption("boundChecks"):
if unlikely(x == low(T)):
if unlikely(x == low(type x)):
raise newException(OverflowError, "The lowest negative number cannot be negated")
result = not x
result += one(T)
result += one(type x)
func abs*[T: IntImpl](x: T): T {.inline.}=
## Returns the absolute value of a signed int.

View File

@ -7,35 +7,23 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./as_words
import ./datatypes
func `not`*(x: UintImpl): UintImpl {.inline.}=
## Bitwise complement of unsigned integer x
{.push experimental: "forLoopMacros".}
for wr, wx in asWords(result, x):
wr = not wx
{.pop.}
applyHiLo(x, `not`)
func `or`*(x, y: UintImpl): UintImpl {.inline.}=
## `Bitwise or` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx or wy
{.pop.}
applyHiLo(x, y, `or`)
func `and`*(x, y: UintImpl): UintImpl {.inline.}=
## `Bitwise and` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx and wy
{.pop.}
applyHiLo(x, y, `and`)
func `xor`*(x, y: UintImpl): UintImpl {.inline.}=
## `Bitwise xor` of numbers x and y
{.push experimental: "forLoopMacros".}
for wr, wx, wy in asWords(result, x, y):
wr = wx xor wy
{.pop.}
applyHiLo(x, y, `xor`)
func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}
# Forward declaration
@ -45,7 +33,7 @@ func `shl`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
# Note: inlining this poses codegen/aliasing issue when doing `x = x shl 1`
# TODO: would it be better to reimplement this with words iteration?
const halfSize: type(y) = getSize(x) div 2
const halfSize: type(y) = bitsof(x) div 2
if y == 0:
return x
@ -61,7 +49,7 @@ func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
## Compute the `shift right` operation of x and y
## Similar to C standard, result is undefined if y is bigger
## than the number of bits in x.
const halfSize: type(y) = getSize(x) div 2
const halfSize: type(y) = bitsof(x) div 2
if y == 0:
return x

View File

@ -7,48 +7,36 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes, ./as_words
import ./datatypes
func isZero*(n: SomeUnsignedInt): bool {.inline.} =
n == 0
func isZero*(n: UintImpl): bool {.inline.} =
{.push experimental: "forLoopMacros".}
for word in asWords(n):
if word != 0:
return false
{.pop.}
return true
n.hi.isZero and n.lo.isZero
func `<`*(x, y: UintImpl): bool {.inline.}=
# Lower comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asWords(x, y):
if wx != wy:
return wx < wy
{.pop.}
return false # they're equal
x.hi < y.hi or
(x.hi == y.hi and x.lo < y.lo)
func `==`*(x, y: UintImpl): bool {.inline.}=
# Equal comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asWords(x, y):
if wx != wy:
return false
{.pop.}
return true # they're equal
x.hi == y.hi and x.lo == y.lo
func `<=`*(x, y: UintImpl): bool {.inline.}=
# Lower or equal comparison for multi-precision integers
{.push experimental: "forLoopMacros".}
for wx, wy in asWords(x, y):
if wx != wy:
return wx < wy
{.pop.}
return true # they're equal
x.hi < y.hi or
(x.hi == y.hi and x.lo <= y.lo)
func isOdd*(x: UintImpl): bool {.inline.}=
bool(x.leastSignificantWord and 1)
func isEven*(x: SomeUnsignedInt): bool {.inline.} =
(x and 1) == 0
func isEven*(x: UintImpl): bool {.inline.}=
not x.isOdd
x.lo.isEven
func isOdd*(x: SomeUnsignedInt): bool {.inline.} =
not x.isEven
func isOdd*(x: UintImpl): bool {.inline.}=
not x.isEven

View File

@ -133,7 +133,7 @@ func div2n1n[T: SomeunsignedInt](q, r: var T, n_hi, n_lo, d: T) =
# assert countLeadingZeroBits(d) == 0, "Divisor was not normalized"
const
size = getSize(q)
size = bitsof(q)
halfSize = size div 2
halfMask = (1.T shl halfSize) - 1.T
@ -238,7 +238,7 @@ func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]=
# TODO: Constant-time division
if unlikely(y.isZero):
raise newException(DivByZeroError, "You attempted to divide by zero")
elif y_clz == (getSize(y) - 1):
elif y_clz == (bitsof(y) - 1):
# y is one
result.quot = x
elif (x.hi or y.hi).isZero:
@ -249,7 +249,7 @@ func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]=
# TODO. Would it be faster to use countTrailingZero (ctz) + clz == size(y) - 1?
# Especially because we shift by ctz after.
# 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
let y_ctz = bitsof(y) - y_clz - 1
result.quot = x shr y_ctz
result.rem = x and (y - one(type y))
elif x == y:

View File

@ -7,7 +7,7 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./private/datatypes, macros
import ./private/datatypes
export StUint
export UintImpl, uintImpl # TODO: remove the need to export those
@ -67,22 +67,15 @@ func isZero*(x: Stuint): bool {.inline.} =
## false otherwise
x.data.isZero
func isOdd*(x: SomeUnSignedInt): bool {.inline.}=
## Returns true if input is odd
## false otherwise
bool(x and 1)
func isEven*(x: SomeUnSignedInt): bool {.inline.}=
## Returns true if input is even
## false otherwise
not x.isOdd
func isOdd*(x: Stuint): bool {.inline.}=
## Returns true if input is odd
## false otherwise
x.data.isOdd
export isEven, isOdd
func isEven*(x: Stuint): bool {.inline.}=
## Returns true if input is even
## false otherwise
x.data.isEven
func isOdd*(x: Stuint): bool {.inline.}=
## Returns true if input is odd
## false otherwise
not x.isEven
import ./private/uint_bitwise_ops