Add randomized testing, harden against edge cases

This commit is contained in:
Mamy Ratsimbazafy 2022-01-25 23:12:52 +01:00 committed by jangko
parent 4660dfe4a4
commit 27e9c9e441
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
10 changed files with 480 additions and 300 deletions

91
benchmarks/bench.nim Normal file
View File

@ -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)

View File

@ -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 # Warmup on normal int to ensure max CPU freq
var start = cpuTime() # Complex enough that the compiler doesn't optimize it away
block:
proc warmup() =
var foo = 123 var foo = 123
bench "Warmup":
for i in 0 ..< 10_000_000: for i in 0 ..< 10_000_000:
foo += i*i mod 456 foo += i*i mod 456
foo = foo mod 789 foo = foo mod 789
# Compiler shouldn't optimize away the results as cpuTime rely on sideeffects warmup()
var stop = cpuTime()
echo "Warmup: " & $(stop - start) & "s"
#################################### ####################################
let a = [123'u64, 123'u64, 123'u64, 123'u64] let a = [123'u64, 123'u64, 123'u64, 123'u64]
let m = [456'u64, 456'u64, 456'u64, 45'u64] let m = [456'u64, 456'u64, 456'u64, 45'u64]
let aU256 = cast[Stuint[256]](a) proc mul_stint(a, m: array[4, uint64]) =
let mU256 = cast[Stuint[256]](m) let aU256 = cast[Stuint[256]](a)
let mU256 = cast[Stuint[256]](m)
start = cpuTime() bench "Mul (stint)":
block:
var foo = aU256 var foo = aU256
for i in 0 ..< 10_000_000: 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 foo += (foo * foo) mod mU256
stop = cpuTime() mul_stint(a, m)
echo "Library: " & $(stop - start) & "s" mod_stint(a, m)
when defined(bench_ttmath): when defined(bench_ttmath):
# need C++ # need C++
import ttmath, ../tests/ttmath_compat import ttmath, ../tests/ttmath_compat
template tt_u256(a: int): UInt[256] = ttmath.u256(a.uint) proc mul_ttmath(a, m: array[4, uint64]) =
let aU256 = a.astt()
start = cpuTime()
block:
var foo = a.astt()
let mU256 = m.astt() let mU256 = m.astt()
for i in 0 ..< 10_000_000:
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 foo += (foo * foo) mod mU256
stop = cpuTime() mul_ttmath(a, m)
echo "TTMath: " & $(stop - start) & "s" mod_ttmath(a, m)
# On my i5-5257 broadwell with the flags:
# nim c -d:release -d:bench_ttmath
# Warmup: 0.04060799999999999s
# Library: 0.9576759999999999s
# TTMath: 0.758443s
# After PR #54 for compile-time evaluation
# which includes loop unrolling but may bloat the code
# Warmup: 0.03993500000000001s
# Library: 0.848464s

260
helpers/prng_unsafe.nim Normal file
View File

@ -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'

View File

@ -14,6 +14,7 @@ requires "nim >= 1.6.0",
proc test(args, path: string) = proc test(args, path: string) =
if not dirExists "build": if not dirExists "build":
mkDir "build" mkDir "build"
exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") & " " & args & exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") & " " & args &
" --outdir:build -r --hints:off --warnings:off --skipParentCfg" & " --outdir:build -r --hints:off --warnings:off --skipParentCfg" &
" --styleCheck:usages --styleCheck:error " & path " --styleCheck:usages --styleCheck:error " & path
@ -22,23 +23,22 @@ proc test(args, path: string) =
" --outdir:build -r --mm:refc --hints:off --warnings:off --skipParentCfg" & " --outdir:build -r --mm:refc --hints:off --warnings:off --skipParentCfg" &
" --styleCheck:usages --styleCheck:error " & path " --styleCheck:usages --styleCheck:error " & path
task test, "Run all tests - test and production implementation": task test_internal, "Run tests for internal procs":
# Run tests for internal procs - test implementation (StUint[64] = 2x uint32 test "internal"
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"
## quicktest-0.20.0/quicktest.nim(277, 30) Error: cannot evaluate at compile time: BUILTIN_NAMES task test_public_api, "Run all tests - prod implementation (StUint[64] = uint64":
## test "all_tests"
# # Run random tests (debug mode) - test implementation (StUint[64] = 2x uint32)
# test "-d:stint_test", "tests/property_based.nim" task test_uint256_ttmath, "Run random tests Uint256 vs TTMath":
# # Run random tests (release mode) - test implementation (StUint[64] = 2x uint32) requires "https://github.com/alehander42/nim-quicktest >= 0.18.0", "https://github.com/status-im/nim-ttmath"
# test "-d:stint_test -d:release", "tests/property_based.nim" switch("define", "release")
# # Run random tests Uint256 (debug mode) vs TTMath (StUint[256] = 8 x uint32) test "uint256_ttmath", "cpp"
# test "", "tests/property_based.nim"
# # Run random tests Uint256 (release mode) vs TTMath (StUint[256] = 4 x uint64) task test, "Run all tests - test and production implementation":
# test "-d:release", "tests/property_based.nim" 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"

View File

@ -66,6 +66,7 @@ template clearExtraBitsOverMSB*(a: var StUint) =
func usedBitsAndWords*(a: openArray[Word]): tuple[bits, words: int] = func usedBitsAndWords*(a: openArray[Word]): tuple[bits, words: int] =
## Returns the number of used words and bits in a bigInt ## 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 var clz = 0
# Count Leading Zeros # Count Leading Zeros
for i in countdown(a.len-1, 0): for i in countdown(a.len-1, 0):
@ -76,6 +77,7 @@ func usedBitsAndWords*(a: openArray[Word]): tuple[bits, words: int] =
else: else:
clz += WordBitWidth - count - 1 clz += WordBitWidth - count - 1
return (a.len*WordBitWidth - clz, i+1) return (a.len*WordBitWidth - clz, i+1)
return (0, 0)
{.pop.} {.pop.}

View File

@ -29,6 +29,13 @@ func shortDiv*(a: var Limbs, k: Word): Word =
# dividend = 2^64 * remainder + a[i] # dividend = 2^64 * remainder + a[i]
var hi = result var hi = result
var lo = a[i] 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. # Normalize, shifting the remainder by clz(k) cannot overflow.
hi = (hi shl clz) or (lo shr (WordBitWidth - clz)) hi = (hi shl clz) or (lo shr (WordBitWidth - clz))
lo = lo shl clz lo = lo shl clz
@ -216,6 +223,7 @@ func divmod(q, r: var Stuint,
q = high(Word) # quotient = MaxWord (0b1111...1111) q = high(Word) # quotient = MaxWord (0b1111...1111)
elif a0 == 0 and a1 < m0: # elif q == 0, true quotient = 0 elif a0 == 0 and a1 < m0: # elif q == 0, true quotient = 0
q = 0 q = 0
return q
else: else:
var r: Word var r: Word
div2n1n(q, r, a0, a1, m0) # else instead of being of by 0, 1 or 2 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 (bBits, bLen) = usedBitsAndWords(b)
let rLen = bLen let rLen = bLen
if unlikely(bBits == 0):
raise newException(DivByZeroError, "You attempted to divide by zero")
if aBits < bBits: if aBits < bBits:
# if a uses less bits than b, # if a uses less bits than b,
# a < b, so q = 0 and r = a # a < b, so q = 0 and r = a

View File

@ -15,7 +15,8 @@ import test_uint_bitops2,
test_uint_muldiv, test_uint_muldiv,
test_uint_exp, test_uint_exp,
test_uint_modular_arithmetic, test_uint_modular_arithmetic,
test_uint_endians2 test_uint_endians2,
test_randomized_divmod
import test_int_endianness, import test_int_endianness,
test_int_comparison, test_int_comparison,
@ -26,3 +27,4 @@ import test_int_endianness,
import test_io, import test_io,
test_conversion test_conversion

View File

@ -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)

View File

@ -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)

View File

@ -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." echo "Testing in release mode with " & $itercount & " random tests for each proc."
else: else:
echo "Testing in debug mode " & $itercount & " random tests for each proc. (StUint[64] = 2x uint32)" 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) let hi = 1'u shl (sizeof(uint64)*7)