diff --git a/benchmarks/bench.nim b/benchmarks/bench.nim new file mode 100644 index 0000000..36d9a55 --- /dev/null +++ b/benchmarks/bench.nim @@ -0,0 +1,91 @@ +import ../stint, std/[times, monotimes] + +template bench(desc: string, body: untyped) = + let start = getMonotime() + body + let stop = getMonotime() + echo desc,": ", inMilliseconds(stop-start), " ms" + +# Warmup on normal int to ensure max CPU freq +# Complex enough that the compiler doesn't optimize it away + +proc warmup() = + var foo = 123 + bench "Warmup": + for i in 0 ..< 10_000_000: + foo += i*i mod 456 + foo = foo mod 789 + +warmup() +#################################### + +let a = [123'u64, 123'u64, 123'u64, 123'u64] +let m = [456'u64, 456'u64, 456'u64, 45'u64] + +proc add_stint(a, m: array[4, uint64]) = + let aU256 = cast[Stuint[256]](a) + let mU256 = cast[Stuint[256]](m) + + bench "Add (stint)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += mU256 + foo += aU256 + +proc mul_stint(a, m: array[4, uint64]) = + let aU256 = cast[Stuint[256]](a) + let mU256 = cast[Stuint[256]](m) + + bench "Mul (stint)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) + +proc mod_stint(a, m: array[4, uint64]) = + let aU256 = cast[Stuint[256]](a) + let mU256 = cast[Stuint[256]](m) + + bench "Mod (stint)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) mod mU256 + +add_stint(a, m) +mul_stint(a, m) +mod_stint(a, m) + +when defined(bench_ttmath): + # need C++ + import ttmath, ../tests/ttmath_compat + + proc add_ttmath(a, m: array[4, uint64]) = + let aU256 = a.astt() + let mU256 = m.astt() + + bench "Add (ttmath)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += mU256 + foo += aU256 + + proc mul_ttmath(a, m: array[4, uint64]) = + let aU256 = a.astt() + let mU256 = m.astt() + + bench "Mul (ttmath)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) + + proc mod_ttmath(a, m: array[4, uint64]) = + let aU256 = a.astt() + let mU256 = m.astt() + + bench "Mod (ttmath)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) mod mU256 + + add_ttmath(a, m) + mul_ttmath(a, m) + mod_ttmath(a, m) \ No newline at end of file diff --git a/benchmarks/bench_mod.nim b/benchmarks/bench_mod.nim index 57fabb7..2ec97e7 100644 --- a/benchmarks/bench_mod.nim +++ b/benchmarks/bench_mod.nim @@ -1,59 +1,69 @@ -import ../stint, times +import ../stint, std/[times, monotimes] +template bench(desc: string, body: untyped) = + let start = getMonotime() + body + let stop = getMonotime() + echo desc,": ", inMilliseconds(stop-start), " ms" -# Warmup on normal int -var start = cpuTime() -block: +# Warmup on normal int to ensure max CPU freq +# Complex enough that the compiler doesn't optimize it away + +proc warmup() = var foo = 123 - for i in 0 ..< 10_000_000: - foo += i*i mod 456 - foo = foo mod 789 - -# Compiler shouldn't optimize away the results as cpuTime rely on sideeffects -var stop = cpuTime() -echo "Warmup: " & $(stop - start) & "s" + bench "Warmup": + for i in 0 ..< 10_000_000: + foo += i*i mod 456 + foo = foo mod 789 +warmup() #################################### let a = [123'u64, 123'u64, 123'u64, 123'u64] let m = [456'u64, 456'u64, 456'u64, 45'u64] -let aU256 = cast[Stuint[256]](a) -let mU256 = cast[Stuint[256]](m) +proc mul_stint(a, m: array[4, uint64]) = + let aU256 = cast[Stuint[256]](a) + let mU256 = cast[Stuint[256]](m) -start = cpuTime() -block: - var foo = aU256 - for i in 0 ..< 10_000_000: - foo += (foo * foo) mod mU256 + bench "Mul (stint)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) -stop = cpuTime() -echo "Library: " & $(stop - start) & "s" +proc mod_stint(a, m: array[4, uint64]) = + let aU256 = cast[Stuint[256]](a) + let mU256 = cast[Stuint[256]](m) + + bench "Mod (stint)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) mod mU256 + +mul_stint(a, m) +mod_stint(a, m) when defined(bench_ttmath): # need C++ import ttmath, ../tests/ttmath_compat - template tt_u256(a: int): UInt[256] = ttmath.u256(a.uint) - - start = cpuTime() - block: - var foo = a.astt() + proc mul_ttmath(a, m: array[4, uint64]) = + let aU256 = a.astt() let mU256 = m.astt() - for i in 0 ..< 10_000_000: - foo += (foo * foo) mod mU256 - stop = cpuTime() - echo "TTMath: " & $(stop - start) & "s" + bench "Mul (ttmath)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) -# On my i5-5257 broadwell with the flags: -# nim c -d:release -d:bench_ttmath -# Warmup: 0.04060799999999999s -# Library: 0.9576759999999999s -# TTMath: 0.758443s + proc mod_ttmath(a, m: array[4, uint64]) = + let aU256 = a.astt() + let mU256 = m.astt() + bench "Mod (ttmath)": + var foo = aU256 + for i in 0 ..< 100_000_000: + foo += (foo * foo) mod mU256 -# After PR #54 for compile-time evaluation -# which includes loop unrolling but may bloat the code -# Warmup: 0.03993500000000001s -# Library: 0.848464s + mul_ttmath(a, m) + mod_ttmath(a, m) \ No newline at end of file diff --git a/helpers/prng_unsafe.nim b/helpers/prng_unsafe.nim new file mode 100644 index 0000000..dba9758 --- /dev/null +++ b/helpers/prng_unsafe.nim @@ -0,0 +1,260 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + ../stint/io, + ../stint/private/datatypes + +# ############################################################ +# +# Pseudo-Random Number Generator +# for testing and benchmarking purposes +# +# ############################################################ +# +# The recommendation by Vigna at http://prng.di.unimi.it +# is to have a period of t^2 if we need t values (i.e. about 2^1024) +# but also that for all practical purposes 2^256 period is enough +# +# We use 2^512 since our main use-case is uint256 + +type RngState* = object + ## This is the state of a Xoshiro512** PRNG + ## Unsafe: for testing and benchmarking purposes only + s: array[8, uint64] + +func splitMix64(state: var uint64): uint64 = + state += 0x9e3779b97f4a7c15'u64 + result = state + result = (result xor (result shr 30)) * 0xbf58476d1ce4e5b9'u64 + result = (result xor (result shr 27)) * 0xbf58476d1ce4e5b9'u64 + result = result xor (result shr 31) + +func seed*(rng: var RngState, x: SomeInteger) = + ## Seed the random number generator with a fixed seed + var sm64 = uint64(x) + rng.s[0] = splitMix64(sm64) + rng.s[1] = splitMix64(sm64) + rng.s[2] = splitMix64(sm64) + rng.s[3] = splitMix64(sm64) + rng.s[4] = splitMix64(sm64) + rng.s[5] = splitMix64(sm64) + rng.s[6] = splitMix64(sm64) + rng.s[7] = splitMix64(sm64) + +func rotl(x: uint64, k: static int): uint64 {.inline.} = + return (x shl k) or (x shr (64 - k)) + +template `^=`(x: var uint64, y: uint64) = + x = x xor y + +func next(rng: var RngState): uint64 = + ## Compute a random uint64 from the input state + ## using xoshiro512** algorithm by Vigna et al + ## State is updated. + result = rotl(rng.s[1] * 5, 7) * 9 + + let t = rng.s[1] shl 11 + rng.s[2] ^= rng.s[0]; + rng.s[5] ^= rng.s[1]; + rng.s[1] ^= rng.s[2]; + rng.s[7] ^= rng.s[3]; + rng.s[3] ^= rng.s[4]; + rng.s[4] ^= rng.s[5]; + rng.s[0] ^= rng.s[6]; + rng.s[6] ^= rng.s[7]; + + rng.s[6] ^= t; + + rng.s[7] = rotl(rng.s[7], 21); + +# Bithacks +# ------------------------------------------------------------ + +proc clearMask[T: SomeInteger](v: T, mask: T): T {.inline.} = + ## Returns ``v``, with all the ``1`` bits from ``mask`` set to 0 + v and not mask + +proc clearBit*[T: SomeInteger](v: T, bit: T): T {.inline.} = + ## Returns ``v``, with the bit at position ``bit`` set to 0 + v.clearMask(1.T shl bit) + +# Integer ranges +# ------------------------------------------------------------ + +func random_unsafe*(rng: var RngState, maxExclusive: uint32): uint32 = + ## Generate a random integer in 0 ..< maxExclusive + ## Uses an unbiaised generation method + ## See Lemire's algorithm modified by Melissa O'Neill + ## https://www.pcg-random.org/posts/bounded-rands.html + let max = maxExclusive + var x = uint32 rng.next() + var m = x.uint64 * max.uint64 + var l = uint32 m + if l < max: + var t = not(max) + 1 # -max + if t >= max: + t -= max + if t >= max: + t = t mod max + while l < t: + x = uint32 rng.next() + m = x.uint64 * max.uint64 + l = uint32 m + return uint32(m shr 32) + +func random_unsafe*[T: SomeInteger](rng: var RngState, inclRange: Slice[T]): T = + ## Return a random integer in the given range. + ## The range bounds must fit in an int32. + let maxExclusive = inclRange.b + 1 - inclRange.a + result = T(rng.random_unsafe(uint32 maxExclusive)) + result += inclRange.a + +# Containers +# ------------------------------------------------------------ + +func sample_unsafe*[T](rng: var RngState, src: openarray[T]): T = + ## Return a random sample from an array + result = src[rng.random_unsafe(uint32 src.len)] + +# BigInts +# ------------------------------------------------------------ +# +# Statistics note: +# - A skewed distribution is not symmetric, it has a longer tail in one direction. +# for example a RNG that is not centered over 0.5 distribution of 0 and 1 but +# might produces more 1 than 0 or vice-versa. +# - A bias is a result that is consistently off from the true value i.e. +# a deviation of an estimate from the quantity under observation + +func random_unsafe(rng: var RngState, a: var SomeBigInteger) = + ## Initialize a standalone BigInt + for i in 0 ..< a.limbs.len: + a.limbs[i] = Word(rng.next()) + +func random_word_highHammingWeight(rng: var RngState): Word = + let numZeros = rng.random_unsafe(WordBitWidth div 3) # Average Hamming Weight is 1-0.33/2 = 0.83 + result = high(Word) + for _ in 0 ..< numZeros: + result = result.clearBit rng.random_unsafe(WordBitWidth) + +func random_highHammingWeight(rng: var RngState, a: var SomeBigInteger) = + ## Initialize a standalone BigInt + ## with high Hamming weight + ## to have a higher probability of triggering carries + for i in 0 ..< a.limbs.len: + a.limbs[i] = Word rng.random_word_highHammingWeight() + +func random_long01Seq(rng: var RngState, a: var openArray[byte]) = + ## Initialize a bytearray + ## It is skewed towards producing strings of 1111... and 0000 + ## to trigger edge cases + # See libsecp256k1: https://github.com/bitcoin-core/secp256k1/blob/dbd41db1/src/testrand_impl.h#L90-L104 + let Bits = a.len * 8 + var bit = 0 + zeroMem(a[0].addr, a.len) + while bit < Bits : + var now = 1 + (rng.random_unsafe(1 shl 6) * rng.random_unsafe(1 shl 5) + 16) div 31 + let val = rng.sample_unsafe([0, 1]) + while now > 0 and bit < Bits: + a[bit shr 3] = a[bit shr 3] or byte(val shl (bit and 7)) + dec now + inc bit + +func random_long01Seq(rng: var RngState, a: var SomeBigInteger) = + ## Initialize a bigint + ## It is skewed towards producing strings of 1111... and 0000 + ## to trigger edge cases + var buf: array[(a.bits + 7) div 8, byte] + rng.random_long01Seq(buf) + a = (typeof a).fromBytesBE(buf) + +# Byte sequences +# ------------------------------------------------------------ + +func random_byte_seq*(rng: var RngState, length: int): seq[byte] = + result.newSeq(length) + for b in result.mitems: + b = byte rng.next() + +# Generic over any Stint type +# ------------------------------------------------------------ + +func random_unsafe*(rng: var RngState, T: typedesc): T = + ## Create a random Field or Extension Field or Curve Element + ## Unsafe: for testing and benchmarking purposes only + rng.random_unsafe(result) + +func random_highHammingWeight*(rng: var RngState, T: typedesc): T = + ## Create a random Field or Extension Field or Curve Element + ## Skewed towards high Hamming Weight + rng.random_highHammingWeight(result) + +func random_long01Seq*(rng: var RngState, T: typedesc): T = + ## Create a random Field or Extension Field or Curve Element + ## Skewed towards long bitstrings of 0 or 1 + rng.random_long01Seq(result) + +type + RandomGen* = enum + Uniform + HighHammingWeight + Long01Sequence + +func random_elem*(rng: var RngState, T: typedesc, gen: RandomGen): T {.inline, noInit.} = + case gen + of Uniform: + result = rng.random_unsafe(T) + of HighHammingWeight: + result = rng.random_highHammingWeight(T) + of Long01Sequence: + result = rng.random_long01Seq(T) + +# Sanity checks +# ------------------------------------------------------------ + +when isMainModule: + import std/[tables, times, strutils] + + var rng: RngState + let timeSeed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 + rng.seed(timeSeed) + echo "prng_sanity_checks xoshiro512** seed: ", timeSeed + + + proc test[T](s: Slice[T]) = + var c = initCountTable[int]() + + for _ in 0 ..< 1_000_000: + c.inc(rng.random_unsafe(s)) + + echo "1'000'000 pseudo-random outputs from ", s.a, " to ", s.b, " (incl): ", c + + test(0..1) + test(0..2) + test(1..52) + test(-10..10) + + echo "\n-----------------------------\n" + echo "High Hamming Weight check" + for _ in 0 ..< 10: + let word = rng.random_word_highHammingWeight() + echo "0b", cast[BiggestInt](word).toBin(WordBitWidth), " - 0x", word.toHex() + + echo "\n-----------------------------\n" + echo "Long strings of 0 or 1 check" + for _ in 0 ..< 10: + var a: BigInt[127] + rng.random_long01seq(a) + stdout.write "0b" + for word in a.limbs: + stdout.write cast[BiggestInt](word).toBin(WordBitWidth) + stdout.write " - 0x" + for word in a.limbs: + stdout.write word.BaseType.toHex() + stdout.write '\n' diff --git a/stint.nimble b/stint.nimble index 3eeacfd..6f67392 100644 --- a/stint.nimble +++ b/stint.nimble @@ -14,6 +14,7 @@ requires "nim >= 1.6.0", proc test(args, path: string) = if not dirExists "build": mkDir "build" + exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") & " " & args & " --outdir:build -r --hints:off --warnings:off --skipParentCfg" & " --styleCheck:usages --styleCheck:error " & path @@ -22,23 +23,22 @@ proc test(args, path: string) = " --outdir:build -r --mm:refc --hints:off --warnings:off --skipParentCfg" & " --styleCheck:usages --styleCheck:error " & path -task test, "Run all tests - test and production implementation": - # Run tests for internal procs - test implementation (StUint[64] = 2x uint32 - test "-d:stint_test", "tests/internal.nim" - # Run tests for internal procs - prod implementation (StUint[64] = uint64 - test "", "tests/internal.nim" - # Run all tests - test implementation (StUint[64] = 2x uint32 - test "-d:stint_test", "tests/all_tests.nim" - # Run all tests - prod implementation (StUint[64] = uint64 - test "--threads:on", "tests/all_tests.nim" +task test_internal, "Run tests for internal procs": + test "internal" - ## quicktest-0.20.0/quicktest.nim(277, 30) Error: cannot evaluate at compile time: BUILTIN_NAMES - ## - # # Run random tests (debug mode) - test implementation (StUint[64] = 2x uint32) - # test "-d:stint_test", "tests/property_based.nim" - # # Run random tests (release mode) - test implementation (StUint[64] = 2x uint32) - # test "-d:stint_test -d:release", "tests/property_based.nim" - # # Run random tests Uint256 (debug mode) vs TTMath (StUint[256] = 8 x uint32) - # test "", "tests/property_based.nim" - # # Run random tests Uint256 (release mode) vs TTMath (StUint[256] = 4 x uint64) - # test "-d:release", "tests/property_based.nim" +task test_public_api, "Run all tests - prod implementation (StUint[64] = uint64": + test "all_tests" + +task test_uint256_ttmath, "Run random tests Uint256 vs TTMath": + requires "https://github.com/alehander42/nim-quicktest >= 0.18.0", "https://github.com/status-im/nim-ttmath" + switch("define", "release") + test "uint256_ttmath", "cpp" + +task test, "Run all tests - test and production implementation": + exec "nimble test_internal" + exec "nimble test_public_api" + ## TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482 + # exec "nimble test_property_debug" + # exec "nimble test_property_release" + # exec "nimble test_property_uint256_debug" + # exec "nimble test_property_uint256_release" diff --git a/stint/private/datatypes.nim b/stint/private/datatypes.nim index 1c43049..1792304 100644 --- a/stint/private/datatypes.nim +++ b/stint/private/datatypes.nim @@ -66,6 +66,7 @@ template clearExtraBitsOverMSB*(a: var StUint) = func usedBitsAndWords*(a: openArray[Word]): tuple[bits, words: int] = ## Returns the number of used words and bits in a bigInt + ## Returns (0, 0) for all-zeros array (even if technically you need 1 bit and 1 word to encode zero) var clz = 0 # Count Leading Zeros for i in countdown(a.len-1, 0): @@ -76,6 +77,7 @@ func usedBitsAndWords*(a: openArray[Word]): tuple[bits, words: int] = else: clz += WordBitWidth - count - 1 return (a.len*WordBitWidth - clz, i+1) + return (0, 0) {.pop.} diff --git a/stint/private/uint_div.nim b/stint/private/uint_div.nim index bb7ca5d..960d02e 100644 --- a/stint/private/uint_div.nim +++ b/stint/private/uint_div.nim @@ -29,6 +29,13 @@ func shortDiv*(a: var Limbs, k: Word): Word = # dividend = 2^64 * remainder + a[i] var hi = result var lo = a[i] + if hi == 0: + if lo < k: + a[i] = 0 + elif lo == k: + a[i] = 1 + result = 0 + continue # Normalize, shifting the remainder by clz(k) cannot overflow. hi = (hi shl clz) or (lo shr (WordBitWidth - clz)) lo = lo shl clz @@ -216,6 +223,7 @@ func divmod(q, r: var Stuint, q = high(Word) # quotient = MaxWord (0b1111...1111) elif a0 == 0 and a1 < m0: # elif q == 0, true quotient = 0 q = 0 + return q else: var r: Word div2n1n(q, r, a0, a1, m0) # else instead of being of by 0, 1 or 2 @@ -295,6 +303,9 @@ func divRem*( let (bBits, bLen) = usedBitsAndWords(b) let rLen = bLen + if unlikely(bBits == 0): + raise newException(DivByZeroError, "You attempted to divide by zero") + if aBits < bBits: # if a uses less bits than b, # a < b, so q = 0 and r = a diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 45b138d..a1fba07 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -15,7 +15,8 @@ import test_uint_bitops2, test_uint_muldiv, test_uint_exp, test_uint_modular_arithmetic, - test_uint_endians2 + test_uint_endians2, + test_randomized_divmod import test_int_endianness, test_int_comparison, @@ -26,3 +27,4 @@ import test_int_endianness, import test_io, test_conversion + diff --git a/tests/property_based.nim b/tests/property_based.nim deleted file mode 100644 index 640c3f3..0000000 --- a/tests/property_based.nim +++ /dev/null @@ -1,238 +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 ../stint, unittest, quicktest, math - -const itercount = 1000 - -suite "Property-based testing (testing with random inputs) - uint64 on 64-bit / uint32 on 32-bit": - - when defined(release): - echo "Testing in release mode with " & $itercount & " random tests for each proc." - else: - echo "Testing in debug mode " & $itercount & " random tests for each proc. (StUint[64] = 2x uint32)" - when defined(stint_test): - echo "(StUint[64] = 2x uint32)" - else: - echo "(StUint[64] = uint64)" - - let hi = 1'u shl (sizeof(uint)*7) - - quicktest "`or`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx or ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx or ty - - - check(cast[uint](tz) == (x or y)) - - - quicktest "`and`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx and ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx and ty - - check(cast[uint](tz) == (x and y)) - - quicktest "`xor`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx xor ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx xor ty - - check(cast[uint](tz) == (x xor y)) - - quicktest "`not`", itercount do(x: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - tz = not tx - else: - let - tx = cast[StUint[32]](x) - tz = not tx - - check(cast[uint](tz) == (not x)) - - quicktest "`<`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx < ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx < ty - - check(tz == (x < y)) - - - quicktest "`<=`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx <= ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx <= ty - - check(tz == (x <= y)) - - quicktest "`+`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx + ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx + ty - - check(cast[uint](tz) == x+y) - - - quicktest "`-`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx - ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx - ty - - check(cast[uint](tz) == x-y) - - quicktest "`*`", itercount do(x: uint(min=0, max=hi), y: uint(min=0, max=hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx * ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx * ty - - check(cast[uint](tz) == x*y) - - quicktest "`shl`", itercount do(x: uint(min=0, max=hi), y: int(min = 0, max=(sizeof(int)*8-1))): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - tz = tx shl y - else: - let - tx = cast[StUint[32]](x) - tz = tx shl y - - check(cast[uint](tz) == x shl y) - - quicktest "`shr`", itercount do(x: uint(min=0, max=hi), y: int(min = 0, max=(sizeof(int)*8-1))): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - tz = tx shr y - else: - let - tx = cast[StUint[32]](x) - tz = tx shr y - - check(cast[uint](tz) == x shr y) - - quicktest "`mod`", itercount do(x: uint(min=0, max=hi), y: uint(min = 1, max = hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx mod ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx mod ty - - check(cast[uint](tz) == x mod y) - - quicktest "`div`", itercount do(x: uint(min=0, max=hi), y: uint(min = 1, max = hi)): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - ty = cast[StUint[64]](y) - tz = tx div ty - else: - let - tx = cast[StUint[32]](x) - ty = cast[StUint[32]](y) - tz = tx div ty - - check(cast[uint](tz) == x div y) - - quicktest "pow", itercount do(x: uint(min=0, max=hi), y: int(min = 0, max = high(int))): - - when sizeof(int) == 8: - let - tx = cast[StUint[64]](x) - tz = tx.pow(y) - - ty = cast[StUint[64]](y) - tz2 = tx.pow(ty) - else: - let - tx = cast[StUint[32]](x) - tz = tx.pow(y) - - ty = cast[StUint[32]](y) - tz2 = tx.pow(ty) - - check(cast[uint](tz) == x ^ y) - check(cast[uint](tz2) == x ^ y) diff --git a/tests/t_randomized_divmod.nim b/tests/t_randomized_divmod.nim new file mode 100644 index 0000000..c0e2e71 --- /dev/null +++ b/tests/t_randomized_divmod.nim @@ -0,0 +1,46 @@ +# Stint +# Copyright 2022 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 + # Standard library + std/[unittest, times], + # Internal + ../stint, + # Test utilities + ../helpers/prng_unsafe + +const Iters = 50000 + +var rng: RngState +let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 +rng.seed(seed) +echo "\n------------------------------------------------------\n" +echo "t_randomized_divmod xoshiro512** seed: ", seed + +proc test_divmod(bits: static int, iters: int, gen: RandomGen) = + for _ in 0 ..< iters: + let a = rng.random_elem(Stuint[bits], gen) + let b = rng.random_elem(Stuint[bits], gen) + + try: + let (q, r) = divmod(a, b) + doAssert a == q*b + r + except DivByZeroDefect: + doAssert b.isZero() + +template test(bits: static int) = + test "(q, r) = divmod(a, b) <=> a = q*b + r (" & $bits & " bits)": + test_divmod(bits, Iters, Uniform) + test_divmod(bits, Iters, HighHammingWeight) + test_divmod(bits, Iters, Long01Sequence) + +suite "Randomized division and modulo checks": + test(128) + test(256) + test(512) \ No newline at end of file diff --git a/tests/property_based_uint256.nim b/tests/test_uint256_ttmath.nim similarity index 99% rename from tests/property_based_uint256.nim rename to tests/test_uint256_ttmath.nim index d722a06..8203737 100644 --- a/tests/property_based_uint256.nim +++ b/tests/test_uint256_ttmath.nim @@ -20,10 +20,6 @@ suite "Property-based testing (testing with random inputs) of Uint256": echo "Testing in release mode with " & $itercount & " random tests for each proc." else: echo "Testing in debug mode " & $itercount & " random tests for each proc. (StUint[64] = 2x uint32)" - when defined(stint_test): - echo "(StUint[64] = 2x uint32)" - else: - echo "(StUint[64] = uint64)" let hi = 1'u shl (sizeof(uint64)*7)