Add randomized testing, harden against edge cases
This commit is contained in:
parent
4660dfe4a4
commit
27e9c9e441
|
@ -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)
|
|
@ -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
|
||||||
for i in 0 ..< 10_000_000:
|
bench "Warmup":
|
||||||
foo += i*i mod 456
|
for i in 0 ..< 10_000_000:
|
||||||
foo = foo mod 789
|
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"
|
|
||||||
|
|
||||||
|
warmup()
|
||||||
####################################
|
####################################
|
||||||
|
|
||||||
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 ..< 100_000_000:
|
||||||
for i in 0 ..< 10_000_000:
|
foo += (foo * foo)
|
||||||
foo += (foo * foo) mod mU256
|
|
||||||
|
|
||||||
stop = cpuTime()
|
proc mod_stint(a, m: array[4, uint64]) =
|
||||||
echo "Library: " & $(stop - start) & "s"
|
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):
|
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:
|
|
||||||
foo += (foo * foo) mod mU256
|
|
||||||
|
|
||||||
stop = cpuTime()
|
bench "Mul (ttmath)":
|
||||||
echo "TTMath: " & $(stop - start) & "s"
|
var foo = aU256
|
||||||
|
for i in 0 ..< 100_000_000:
|
||||||
|
foo += (foo * foo)
|
||||||
|
|
||||||
# On my i5-5257 broadwell with the flags:
|
proc mod_ttmath(a, m: array[4, uint64]) =
|
||||||
# nim c -d:release -d:bench_ttmath
|
let aU256 = a.astt()
|
||||||
# Warmup: 0.04060799999999999s
|
let mU256 = m.astt()
|
||||||
# Library: 0.9576759999999999s
|
|
||||||
# TTMath: 0.758443s
|
|
||||||
|
|
||||||
|
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
|
mul_ttmath(a, m)
|
||||||
# which includes loop unrolling but may bloat the code
|
mod_ttmath(a, m)
|
||||||
# Warmup: 0.03993500000000001s
|
|
||||||
# Library: 0.848464s
|
|
|
@ -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'
|
38
stint.nimble
38
stint.nimble
|
@ -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"
|
||||||
|
|
|
@ -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.}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue