mirror of
https://github.com/status-im/nim-stint.git
synced 2025-02-16 17:07:23 +00:00
Add randomized testing vs GMP
This commit is contained in:
parent
27e9c9e441
commit
bff69b3f98
@ -1,69 +0,0 @@
|
|||||||
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 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
|
|
||||||
|
|
||||||
mul_stint(a, m)
|
|
||||||
mod_stint(a, m)
|
|
||||||
|
|
||||||
when defined(bench_ttmath):
|
|
||||||
# need C++
|
|
||||||
import ttmath, ../tests/ttmath_compat
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
mul_ttmath(a, m)
|
|
||||||
mod_ttmath(a, m)
|
|
@ -172,7 +172,7 @@ func random_long01Seq(rng: var RngState, a: var SomeBigInteger) =
|
|||||||
## to trigger edge cases
|
## to trigger edge cases
|
||||||
var buf: array[(a.bits + 7) div 8, byte]
|
var buf: array[(a.bits + 7) div 8, byte]
|
||||||
rng.random_long01Seq(buf)
|
rng.random_long01Seq(buf)
|
||||||
a = (typeof a).fromBytesBE(buf)
|
a = (typeof a).fromBytes(buf, bigEndian)
|
||||||
|
|
||||||
# Byte sequences
|
# Byte sequences
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
29
helpers/staticfor.nim
Normal file
29
helpers/staticfor.nim
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import std/macros
|
||||||
|
|
||||||
|
proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode =
|
||||||
|
# Replace "what" ident node by "by"
|
||||||
|
proc inspect(node: NimNode): NimNode =
|
||||||
|
case node.kind:
|
||||||
|
of {nnkIdent, nnkSym}:
|
||||||
|
if node.eqIdent(what):
|
||||||
|
return by
|
||||||
|
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 staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped =
|
||||||
|
## staticFor [min inclusive, max exclusive)
|
||||||
|
result = newStmtList()
|
||||||
|
for i in start ..< stopEx:
|
||||||
|
result.add nnkBlockStmt.newTree(
|
||||||
|
ident("unrolledIter_" & $idx & $i),
|
||||||
|
body.replaceNodes(idx, newLit i)
|
||||||
|
)
|
10
stint.nimble
10
stint.nimble
@ -1,5 +1,5 @@
|
|||||||
packageName = "stint"
|
packageName = "stint"
|
||||||
version = "0.0.1"
|
version = "2.0.0"
|
||||||
author = "Status Research & Development GmbH"
|
author = "Status Research & Development GmbH"
|
||||||
description = "Efficient stack-based multiprecision int in Nim"
|
description = "Efficient stack-based multiprecision int in Nim"
|
||||||
license = "Apache License 2.0 or MIT"
|
license = "Apache License 2.0 or MIT"
|
||||||
@ -9,7 +9,6 @@ skipDirs = @["tests", "benchmarks"]
|
|||||||
# TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482
|
# TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482
|
||||||
requires "nim >= 1.6.0",
|
requires "nim >= 1.6.0",
|
||||||
"stew"
|
"stew"
|
||||||
#, "https://github.com/alehander42/nim-quicktest >= 0.18.0", "https://github.com/status-im/nim-ttmath"
|
|
||||||
|
|
||||||
proc test(args, path: string) =
|
proc test(args, path: string) =
|
||||||
if not dirExists "build":
|
if not dirExists "build":
|
||||||
@ -34,11 +33,6 @@ task test_uint256_ttmath, "Run random tests Uint256 vs TTMath":
|
|||||||
switch("define", "release")
|
switch("define", "release")
|
||||||
test "uint256_ttmath", "cpp"
|
test "uint256_ttmath", "cpp"
|
||||||
|
|
||||||
task test, "Run all tests - test and production implementation":
|
task test, "Run all tests":
|
||||||
exec "nimble test_internal"
|
exec "nimble test_internal"
|
||||||
exec "nimble test_public_api"
|
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"
|
|
||||||
|
@ -126,7 +126,8 @@ func toBytesBE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] {.
|
|||||||
result[tail-1-i] = toByte(lo shr ((tail-i)*8))
|
result[tail-1-i] = toByte(lo shr ((tail-i)*8))
|
||||||
return
|
return
|
||||||
|
|
||||||
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = system.cpuEndian): array[bits div 8, byte] {.inline.} =
|
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = bigEndian): array[bits div 8, byte] {.inline.} =
|
||||||
|
## Default to bigEndian
|
||||||
if endian == littleEndian:
|
if endian == littleEndian:
|
||||||
result = x.toBytesLE()
|
result = x.toBytesLE()
|
||||||
else:
|
else:
|
||||||
@ -238,10 +239,11 @@ func fromBytesLE*[bits: static int](
|
|||||||
func fromBytes*[bits: static int](
|
func fromBytes*[bits: static int](
|
||||||
T: typedesc[StUint[bits]],
|
T: typedesc[StUint[bits]],
|
||||||
x: openarray[byte],
|
x: openarray[byte],
|
||||||
srcEndian: Endianness = system.cpuEndian): T {.inline.} =
|
srcEndian: Endianness = bigEndian): T {.inline.} =
|
||||||
## Read an source bytearray with the specified endianness and
|
## Read an source bytearray with the specified endianness and
|
||||||
## convert it to an integer
|
## convert it to an integer
|
||||||
when srcEndian == littleEndian:
|
## Default to bigEndian
|
||||||
|
if srcEndian == littleEndian:
|
||||||
result = fromBytesLE(T, x)
|
result = fromBytesLE(T, x)
|
||||||
else:
|
else:
|
||||||
result = fromBytesBE(T, x)
|
result = fromBytesBE(T, x)
|
||||||
|
197
tests/t_randomized_vs_gmp.nim
Normal file
197
tests/t_randomized_vs_gmp.nim
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# Stint
|
||||||
|
# Copyright (c) 2018-2022 Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# Standard library
|
||||||
|
std/[unittest, times, strutils],
|
||||||
|
# Third-party
|
||||||
|
gmp, stew/byteutils,
|
||||||
|
# Internal
|
||||||
|
../stint,
|
||||||
|
# Test utilities
|
||||||
|
../helpers/[prng_unsafe, staticfor]
|
||||||
|
|
||||||
|
const
|
||||||
|
Iters = 1000
|
||||||
|
Bitwidths = [128, 256, 512, 1024, 2048]
|
||||||
|
|
||||||
|
const # https://gmplib.org/manual/Integer-Import-and-Export.html
|
||||||
|
GMP_WordLittleEndian {.used.} = -1'i32
|
||||||
|
GMP_WordNativeEndian {.used.} = 0'i32
|
||||||
|
GMP_WordBigEndian {.used.} = 1'i32
|
||||||
|
|
||||||
|
GMP_MostSignificantWordFirst = 1'i32
|
||||||
|
GMP_LeastSignificantWordFirst {.used.} = -1'i32
|
||||||
|
|
||||||
|
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_vs_gmp xoshiro512** seed: ", seed
|
||||||
|
|
||||||
|
proc rawUint(dst: var openArray[byte], src: mpz_t): csize =
|
||||||
|
## Converts a GMP bigint to a canonical integer as a BigEndian array of byte
|
||||||
|
## Returns the number of words actually written
|
||||||
|
discard mpz_export(dst[0].addr, result.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, src)
|
||||||
|
|
||||||
|
proc fromStuint[bits: static int](dst: var mpz_t, src: Stuint[bits]) =
|
||||||
|
let t = src.toBytes()
|
||||||
|
mpz_import(dst, t.len, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, t[0].addr)
|
||||||
|
|
||||||
|
# Sanity check
|
||||||
|
var t2: typeof(t)
|
||||||
|
let wordsWritten = t2.rawUint(dst)
|
||||||
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
||||||
|
doAssert t2.toOpenArray(0, wordsWritten-1) == t.toOpenArray(t.len-wordsWritten, t.len-1)
|
||||||
|
|
||||||
|
proc test_add(bits: static int, iters: int, gen: RandomGen) =
|
||||||
|
|
||||||
|
const N = (bits + 7) div 8
|
||||||
|
|
||||||
|
var x, y, z, m: mpz_t
|
||||||
|
mpz_init(x)
|
||||||
|
mpz_init(y)
|
||||||
|
mpz_init(z)
|
||||||
|
mpz_init(m)
|
||||||
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
||||||
|
|
||||||
|
for _ in 0 ..< iters:
|
||||||
|
let a = rng.random_elem(Stuint[bits], gen)
|
||||||
|
let b = rng.random_elem(Stuint[bits], gen)
|
||||||
|
|
||||||
|
x.fromStuint(a)
|
||||||
|
y.fromStuint(b)
|
||||||
|
|
||||||
|
let c = a + b
|
||||||
|
mpz_add(z, x, y)
|
||||||
|
mpz_mod(z, z, m)
|
||||||
|
|
||||||
|
let cBytes = c.toBytes()
|
||||||
|
|
||||||
|
var zBytes: array[N, byte]
|
||||||
|
let wordsWritten = zBytes.rawUint(z)
|
||||||
|
|
||||||
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
||||||
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
||||||
|
# Reexport as bigEndian for debugging
|
||||||
|
var xBuf, yBuf: array[N, byte]
|
||||||
|
discard xBuf.rawUint(x)
|
||||||
|
discard yBuf.rawUint(y)
|
||||||
|
"\nAddition with operands\n" &
|
||||||
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
||||||
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
||||||
|
"failed:" & "\n" &
|
||||||
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
||||||
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
||||||
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
||||||
|
|
||||||
|
template testAddition(bits: static int) =
|
||||||
|
test "Addition vs GMP (" & $bits & " bits)":
|
||||||
|
test_add(bits, Iters, Uniform)
|
||||||
|
test_add(bits, Iters, HighHammingWeight)
|
||||||
|
test_add(bits, Iters, Long01Sequence)
|
||||||
|
|
||||||
|
proc test_sub(bits: static int, iters: int, gen: RandomGen) =
|
||||||
|
|
||||||
|
const N = (bits + 7) div 8
|
||||||
|
|
||||||
|
var x, y, z, m: mpz_t
|
||||||
|
mpz_init(x)
|
||||||
|
mpz_init(y)
|
||||||
|
mpz_init(z)
|
||||||
|
mpz_init(m)
|
||||||
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
||||||
|
|
||||||
|
for _ in 0 ..< iters:
|
||||||
|
let a = rng.random_elem(Stuint[bits], gen)
|
||||||
|
let b = rng.random_elem(Stuint[bits], gen)
|
||||||
|
|
||||||
|
x.fromStuint(a)
|
||||||
|
y.fromStuint(b)
|
||||||
|
|
||||||
|
let c = a - b
|
||||||
|
mpz_sub(z, x, y)
|
||||||
|
mpz_mod(z, z, m)
|
||||||
|
|
||||||
|
let cBytes = c.toBytes()
|
||||||
|
|
||||||
|
var zBytes: array[N, byte]
|
||||||
|
let wordsWritten = zBytes.rawUint(z)
|
||||||
|
|
||||||
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
||||||
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
||||||
|
# Reexport as bigEndian for debugging
|
||||||
|
var xBuf, yBuf: array[N, byte]
|
||||||
|
discard xBuf.rawUint(x)
|
||||||
|
discard yBuf.rawUint(y)
|
||||||
|
"\nSubstraction with operands\n" &
|
||||||
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
||||||
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
||||||
|
"failed:" & "\n" &
|
||||||
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
||||||
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
||||||
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
||||||
|
|
||||||
|
template testSubstraction(bits: static int) =
|
||||||
|
test "Substaction vs GMP (" & $bits & " bits)":
|
||||||
|
test_sub(bits, Iters, Uniform)
|
||||||
|
test_sub(bits, Iters, HighHammingWeight)
|
||||||
|
test_sub(bits, Iters, Long01Sequence)
|
||||||
|
|
||||||
|
proc test_mul(bits: static int, iters: int, gen: RandomGen) =
|
||||||
|
|
||||||
|
const N = (bits + 7) div 8
|
||||||
|
|
||||||
|
var x, y, z, m: mpz_t
|
||||||
|
mpz_init(x)
|
||||||
|
mpz_init(y)
|
||||||
|
mpz_init(z)
|
||||||
|
mpz_init(m)
|
||||||
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
||||||
|
|
||||||
|
for _ in 0 ..< iters:
|
||||||
|
let a = rng.random_elem(Stuint[bits], gen)
|
||||||
|
let b = rng.random_elem(Stuint[bits], gen)
|
||||||
|
|
||||||
|
x.fromStuint(a)
|
||||||
|
y.fromStuint(b)
|
||||||
|
|
||||||
|
let c = a * b
|
||||||
|
mpz_mul(z, x, y)
|
||||||
|
mpz_mod(z, z, m)
|
||||||
|
|
||||||
|
let cBytes = c.toBytes()
|
||||||
|
|
||||||
|
var zBytes: array[N, byte]
|
||||||
|
let wordsWritten = zBytes.rawUint(z)
|
||||||
|
|
||||||
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
||||||
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
||||||
|
# Reexport as bigEndian for debugging
|
||||||
|
var xBuf, yBuf: array[N, byte]
|
||||||
|
discard xBuf.rawUint(x)
|
||||||
|
discard yBuf.rawUint(y)
|
||||||
|
"\nMultiplication with operands\n" &
|
||||||
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
||||||
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
||||||
|
"failed:" & "\n" &
|
||||||
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
||||||
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
||||||
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
||||||
|
|
||||||
|
template testMultiplication(bits: static int) =
|
||||||
|
test "Multiplication vs GMP (" & $bits & " bits)":
|
||||||
|
test_mul(bits, Iters, Uniform)
|
||||||
|
test_mul(bits, Iters, HighHammingWeight)
|
||||||
|
test_mul(bits, Iters, Long01Sequence)
|
||||||
|
|
||||||
|
suite "Randomized arithmetic tests vs GMP":
|
||||||
|
staticFor i, 0, Bitwidths.len:
|
||||||
|
testAddition(Bitwidths[i])
|
||||||
|
testSubstraction(Bitwidths[i])
|
||||||
|
testMultiplication(Bitwidths[i])
|
Loading…
x
Reference in New Issue
Block a user