From 7e57fcecb87e3ffb9ba1afb0e823bcb43d1304a3 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Tue, 2 Aug 2022 08:38:18 +0200 Subject: [PATCH 1/8] Add a initRandomBigInt proc --- src/bigints.nim | 16 +++++++++++++++- tests/tbigints.nim | 8 +++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/bigints.nim b/src/bigints.nim index 9ef5abb..b528496 100644 --- a/src/bigints.nim +++ b/src/bigints.nim @@ -1,6 +1,6 @@ ## Arbitrary precision integers. -import std/[algorithm, bitops, math, options] +import std/[algorithm, bitops, math, options, random] type BigInt* = object @@ -1198,3 +1198,17 @@ func powmod*(base, exponent, modulus: BigInt): BigInt = result = (result * basePow) mod modulus basePow = (basePow * basePow) mod modulus exponent = exponent shr 1 + +# proc initRandomBigInt*(nbits: Natural): BigInt = +# ## Initialize a standalone BigInt +# let n_limbs = nbits shr 5 +# let remainingBits = nbits mod 32 +# result.limbs.setLen(n_limbs) +# let mask: uint32 = 1'u32 shl (remainingBits) - 1 +# for i in 0 ..< result.limbs.len-1: +# result.limbs[i] = rand(uint32) +# result.limbs[result.limbs.len-1] = rand(uint32) xor mask + +# when isMainModule: +# randomize() + diff --git a/tests/tbigints.nim b/tests/tbigints.nim index 3b3a59d..148567a 100644 --- a/tests/tbigints.nim +++ b/tests/tbigints.nim @@ -1,5 +1,6 @@ import bigints import std/options +import random const zero = initBigInt(0) @@ -786,6 +787,11 @@ proc main() = doAssert pred(a, 3) == initBigInt(4) doAssert succ(a, 3) == initBigInt(10) + # block: + # randomize() + # let a: BigInt = initRandomBigInt(33) + # echo a -static: main() + +#static: main() main() From 49a781763aa51ff36cd730125bdd770c60432143 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Tue, 2 Aug 2022 08:38:18 +0200 Subject: [PATCH 2/8] Add and test initRandomBigInt --- bigints.nimble | 2 +- src/bigints.nim | 24 +++++++++++++++++++++++- tests/trandom.nim | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/trandom.nim diff --git a/bigints.nimble b/bigints.nimble index 5de9609..b2659c7 100644 --- a/bigints.nimble +++ b/bigints.nimble @@ -16,7 +16,7 @@ task test, "Test bigints": echo "testing " & backend & " backend" for gc in ["refc", "arc", "orc"]: echo " using " & gc & " GC" - for file in ["tbigints.nim", "tbugs.nim"]: + for file in ["trandom.nim", "tbigints.nim", "tbugs.nim"]: exec "nim r --hints:off --experimental:strictFuncs --backend:" & backend & " --gc:" & gc & " tests/" & file exec "nim doc --hints:off --backend:" & backend & " --gc:" & gc & " src/bigints.nim" diff --git a/src/bigints.nim b/src/bigints.nim index 9ef5abb..cb9352e 100644 --- a/src/bigints.nim +++ b/src/bigints.nim @@ -1,6 +1,6 @@ ## Arbitrary precision integers. -import std/[algorithm, bitops, math, options] +import std/[algorithm, bitops, math, options, random] type BigInt* = object @@ -66,6 +66,27 @@ else: func initBigInt*(val: BigInt): BigInt = result = val +proc initRandomBigInt*(nbits: Natural): BigInt = + ## Initializes a standalone BigInt with exactly `nbits` bits. + let + remainder = nbits mod 32 + n_limbs = (if remainder == 0: nbits shr 5 else: nbits shr 5 + 1) + remainingBits = (if remainder == 0: 32 else: remainder) + result.limbs.setLen(n_limbs) + + # mask ensures only remainingBits bits can be set to 1 + # Ensures the first bit is set to 1 + var + mask: uint32 = 0xFFFF_FFFF'u32 + mask2: uint32 = 0x8000_0000'u32 + if remainingBits != 32: + mask = 1'u32 shl remainingBits - 1 + mask2 = 1'u32 shl (remainingBits-1) + for i in 0 ..< result.limbs.len-1: + result.limbs[i] = rand(uint32) + let word = rand(uint32) + result.limbs[result.limbs.len-1] = word and mask or mask2 + const zero = initBigInt(0) one = initBigInt(1) @@ -1198,3 +1219,4 @@ func powmod*(base, exponent, modulus: BigInt): BigInt = result = (result * basePow) mod modulus basePow = (basePow * basePow) mod modulus exponent = exponent shr 1 + diff --git a/tests/trandom.nim b/tests/trandom.nim new file mode 100644 index 0000000..9ebba04 --- /dev/null +++ b/tests/trandom.nim @@ -0,0 +1,18 @@ +import bigints +import random + +const + zero = initBigInt(0) + one = initBigInt(1) + +proc main() = + block: + randomize() + # Repeat probabilistic tests + for nBits in [29, 32, 1037]: + for _ in 1 .. 5: + let a: BigInt = initRandomBigInt(nBits) + assert (toString(a, 2)).len == nBits + doAssert fastLog2(a) == (nBits - 1) + +main() From 6c289404f474fbc47e4458194c94f9d374f0512f Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Wed, 10 Aug 2022 20:53:31 +0200 Subject: [PATCH 3/8] Make examples standalone files and export some of their functions --- examples/elliptic.nim | 6 ++--- examples/pidigits.nim | 47 ++++++++++++++++++++-------------------- examples/rc_combperm.nim | 9 ++++---- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/examples/elliptic.nim b/examples/elliptic.nim index 90e25a7..93380c5 100644 --- a/examples/elliptic.nim +++ b/examples/elliptic.nim @@ -35,7 +35,7 @@ proc ecDouble(a: tuple): (BigInt, BigInt) = lam = lam mod primeCurve result = (x, y) -proc ecMultiply(genPoint: tuple, scalarHex: BigInt): (BigInt, BigInt) = +proc ecMultiply*(genPoint: tuple, scalarHex: BigInt): (BigInt, BigInt) = if scalarHex == zero or scalarHex >= numberPoints: raise newException(Exception, "Invalid Scalar/Private Key") var @@ -47,7 +47,7 @@ proc ecMultiply(genPoint: tuple, scalarHex: BigInt): (BigInt, BigInt) = q = ecAdd(q, genPoint) result = q -proc main() = +when isMainModule: let publicKey = ecMultiply(Gpoint, privKey) echo "" @@ -65,5 +65,3 @@ proc main() = echo "the official Public Key - compressed:" echo if publicKey[1] mod two == one: "03" & publicKey[0].toString(base = 16).align(64, '0') else: "02" & publicKey[0].toString(base = 16).align(64, '0') - -main() diff --git a/examples/pidigits.nim b/examples/pidigits.nim index aa8448d..23ec4a5 100644 --- a/examples/pidigits.nim +++ b/examples/pidigits.nim @@ -34,7 +34,7 @@ proc extractDigit(): int32 = result = get(toInt[int32](tmp1 and mask)) -proc eliminateDigit(d: int32) = +proc eliminateDigit*(d: int32) = acc -= den * d.initBigInt acc *= ten num *= ten @@ -48,33 +48,34 @@ proc nextTerm() = den *= k2 num *= k -proc findPiDigit(): int32 = +proc findPiDigit*(): int32 = result = -1 while result < 0: nextTerm() result = extractDigit() -var i = 0 -if paramCount() == 0: - # prints an infinite amount of pi digits - while true: - var d: int32 = findPiDigit() - stdout.write chr(ord('0') + d) - inc i - if i == 40: - echo "" - i = 0 - eliminateDigit(d) +when isMainModule: + var i = 0 + if paramCount() == 0: + # prints an infinite amount of pi digits + while true: + var d: int32 = findPiDigit() + stdout.write chr(ord('0') + d) + inc i + if i == 40: + echo "" + i = 0 + eliminateDigit(d) -let n = parseInt(paramStr(1)) + let n = parseInt(paramStr(1)) -if n <= 0: - quit("The number you entered is negative. Please specify a strictly positive number") + if n <= 0: + quit("The number you entered is negative. Please specify a strictly positive number") -while i < n: - var d: int32 = findPiDigit() - stdout.write(chr(ord('0') + d)) - inc(i) - if i mod 40 == 0: - echo "\t:", i - eliminateDigit(d) + while i < n: + var d: int32 = findPiDigit() + stdout.write(chr(ord('0') + d)) + inc(i) + if i mod 40 == 0: + echo "\t:", i + eliminateDigit(d) diff --git a/examples/rc_combperm.nim b/examples/rc_combperm.nim index dc25dab..9549110 100644 --- a/examples/rc_combperm.nim +++ b/examples/rc_combperm.nim @@ -1,7 +1,7 @@ # Solution for https://rosettacode.org/wiki/Combinations_and_permutations import bigints -proc perm(n, k: int32): BigInt = +proc perm*(n, k: int32): BigInt = result = initBigInt(1) var k = initBigInt(n - k) @@ -10,12 +10,13 @@ proc perm(n, k: int32): BigInt = result *= n dec n -proc comb(n, k: int32): BigInt = +proc comb*(n, k: int32): BigInt = result = perm(n, k) var k = initBigInt(k) while k > 0.initBigInt: result = result div k dec k -echo "P(1000, 969) = ", perm(1000, 969) -echo "C(1000, 969) = ", comb(1000, 969) +when isMainModule: + echo "P(1000, 969) = ", perm(1000, 969) + echo "C(1000, 969) = ", comb(1000, 969) From df8686735cc86b0ac9adcbc65722421ff245d6cb Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Wed, 10 Aug 2022 20:54:08 +0200 Subject: [PATCH 4/8] Benchmark some of the examples --- benchmarks/benchExamples.nim | 35 +++++++++++++++++++++++++++++++++++ benchmarks/nim.cfg | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 benchmarks/benchExamples.nim create mode 100644 benchmarks/nim.cfg diff --git a/benchmarks/benchExamples.nim b/benchmarks/benchExamples.nim new file mode 100644 index 0000000..dcff669 --- /dev/null +++ b/benchmarks/benchExamples.nim @@ -0,0 +1,35 @@ +import bigints +import benchy +from std/math import `^` +import pidigits +import rc_combperm + +block: # Binomial and permutations + timeIt "Permutation 1000, 969": + keep perm(1000, 969) + + timeIt "Binomial computation 1000, 969": + keep comb(1000, 969) + +block: # Power computation + timeIt "Power computation of 5^4^3^2": + keep 5.initBigInt.pow 4 ^ (3 ^ 2) + timeIt "Powers of two": + var power = 2.initBigInt + for _ in 1 .. 128000: + power = power * 2.initBigInt + +block: # Pidigits example + timeIt "Computation of 100 digits of Pi": + var i = 0 + while i < 100: + var d: int32 = findPiDigit() + inc(i) + eliminateDigit(d) + timeIt "Computation of 1000 digits of Pi": + var i = 0 + while i < 1000: + var d: int32 = findPiDigit() + inc(i) + eliminateDigit(d) + diff --git a/benchmarks/nim.cfg b/benchmarks/nim.cfg new file mode 100644 index 0000000..6feaed8 --- /dev/null +++ b/benchmarks/nim.cfg @@ -0,0 +1,2 @@ +path = "$projectPath/../src" +path = "$projectPath/../examples" From c56e7b64e301557ba124404d5806e7a3366c3607 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Fri, 12 Aug 2022 10:59:26 +0200 Subject: [PATCH 5/8] Add two nimble tasks for benchmarking --- bigints.nimble | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bigints.nimble b/bigints.nimble index 5de9609..8d19751 100644 --- a/bigints.nimble +++ b/bigints.nimble @@ -25,3 +25,20 @@ task checkExamples, "Check examples": for example in listFiles("examples"): if example.endsWith(".nim"): exec "nim check --hints:off " & example + +task benchAll, "Benchmark Library": + for backend in ["c", "cpp"]: + echo "Benchmarks with " & backend & " backend" + for gc in ["refc", "arc", "orc"]: + echo "Benchmark with " & gc & " garbage collector" + for benchmark in listFiles("benchmarks"): + if benchmark.endsWith(".nim"): + echo "Benchmark " & benchmark + exec "nim r --hints:off -d:danger --opt:speed -d:lto --backend:" & backend & " --gc:" & gc & " " & benchmark + +task benchOrcC, "Benchmark Library with orc garbage collector and C backend": + for benchmark in listFiles("benchmarks"): + if benchmark.endsWith(".nim"): + echo "Benchmark " & benchmark + exec "nim r --hints:off -d:danger --opt:speed -d:lto --backend:c --gc:orc " & benchmark + From b4a1bebbaeb7570149ff35b406f7a36aa26cdd75 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Fri, 12 Aug 2022 14:20:50 +0200 Subject: [PATCH 6/8] Add a new type and optional parameter to generate random bigints with a fixed number of limbs --- src/bigints.nim | 61 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/bigints.nim b/src/bigints.nim index cb9352e..6357585 100644 --- a/src/bigints.nim +++ b/src/bigints.nim @@ -66,26 +66,49 @@ else: func initBigInt*(val: BigInt): BigInt = result = val -proc initRandomBigInt*(nbits: Natural): BigInt = - ## Initializes a standalone BigInt with exactly `nbits` bits. - let - remainder = nbits mod 32 - n_limbs = (if remainder == 0: nbits shr 5 else: nbits shr 5 + 1) - remainingBits = (if remainder == 0: 32 else: remainder) - result.limbs.setLen(n_limbs) +type + SizeDescriptor* = enum + Limbs, Bits + +proc initRandomBigInt*(number: Natural, unit: SizeDescriptor = Limbs): BigInt = + ## Initializes a standalone BigInt whose value is chosen randomly with exactly + ## `number` bits or limbs, depending on the value of `unit`. By default, the + ## BigInt is chosen with `number` limbs chosen randomly. + if unit == Limbs: + if number == 0: + raise newException(ValueError, "A Bigint must have at least one limb !") + result.limbs.setLen(number) + for i in 0 ..< result.limbs.len-1: + result.limbs[i] = rand(uint32) + var word = rand(uint32) + # Bigint's last limb can be zero, iff there is only one limb + # We can't normalize instead, since we need no less than number limbs + if number != 1: + while word == 0: # Very low probability + word = rand(uint32) + result.limbs[result.limbs.len-1] = word + + else: # unit == Bits + if number == 0: + return initBigInt(0) + let + remainder = number mod 32 + n_limbs = (if remainder == 0: number shr 5 else: number shr 5 + 1) + remainingBits = (if remainder == 0: 32 else: remainder) + result.limbs.setLen(n_limbs) + # mask ensures only remainingBits bits can be set to 1 + # Ensures the first bit is set to 1 + var + mask: uint32 = 0xFFFF_FFFF'u32 + mask2: uint32 = 0x8000_0000'u32 + if remainingBits != 32: + mask = 1'u32 shl remainingBits - 1 + mask2 = 1'u32 shl (remainingBits-1) + for i in 0 ..< result.limbs.len-1: + result.limbs[i] = rand(uint32) + let word = rand(uint32) + result.limbs[result.limbs.len-1] = word and mask or mask2 - # mask ensures only remainingBits bits can be set to 1 - # Ensures the first bit is set to 1 - var - mask: uint32 = 0xFFFF_FFFF'u32 - mask2: uint32 = 0x8000_0000'u32 - if remainingBits != 32: - mask = 1'u32 shl remainingBits - 1 - mask2 = 1'u32 shl (remainingBits-1) - for i in 0 ..< result.limbs.len-1: - result.limbs[i] = rand(uint32) - let word = rand(uint32) - result.limbs[result.limbs.len-1] = word and mask or mask2 const zero = initBigInt(0) From 87b1af3ea5deec0c0098db8552ad0ffc7f339b52 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Fri, 12 Aug 2022 14:27:25 +0200 Subject: [PATCH 7/8] Adapt tests with new random bigints function, add a memory limit, and add tests for gcd --- tests/trandom.nim | 57 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/tests/trandom.nim b/tests/trandom.nim index 9ebba04..5dab63d 100644 --- a/tests/trandom.nim +++ b/tests/trandom.nim @@ -1,18 +1,67 @@ import bigints import random +type + MemSizeUnit = enum + o, Kio, Mio, Gio + const zero = initBigInt(0) one = initBigInt(1) + memSize = 2 # Max number of allocated memory for the tests + memSizeUnit = Mio # Unit in which memSize is expressed + +proc computeLimit(memSize: Natural, memSizeUnit: MemSizeUnit): Natural = + var factor = 1 + for _ in 1..ord(memSizeUnit): + factor *= 1024 + result = memSize * factor + +const + memLimit = computeLimit(memSize, memSizeUnit) # Number of octets + maxLimbs = memLimit div 8 + maxBits = 4*memLimit proc main() = + randomize() + + block: + let a: BigInt = initRandomBigInt(0, Bits) + doAssert a == zero + let b: BigInt = initRandomBigInt(1, Bits) + doAssert b == one + block: - randomize() - # Repeat probabilistic tests for nBits in [29, 32, 1037]: + for _ in 1 .. 5: # Repeat probabilistic tests + let a: BigInt = initRandomBigInt(nBits, Bits) + doAssert fastLog2(a) == (nBits - 1) + doAssert (toString(a, 2)).len == nBits + # For bigger bigints, remove the test with slow conversion to string + for nBits in [rand(1..maxBits), 32*rand(1..maxLimbs)]: for _ in 1 .. 5: - let a: BigInt = initRandomBigInt(nBits) - assert (toString(a, 2)).len == nBits + let a: BigInt = initRandomBigInt(nBits, Bits) doAssert fastLog2(a) == (nBits - 1) + block: + for nLimbs in [1, 2, 3, 5, 10, 25, 100]: + for _ in 1 .. 5: + let a: BigInt = initRandomBigInt(nLimbs) + let n_bitsA = fastLog2(a) + 1 + doAssert n_bitsA <= 32*nlimbs + doAssert n_bitsA > 32*(nlimbs-1) + + block: # GCD properties but tested on random Bigints + let limitGCD = 100_000 # Special limit for the GCD, otherwise the tests run for hours + let (nBitsA, nBitsB, nBitsC) = (rand(1..limitGCD), rand(1..limitGCD), rand(1..limitGCD)) + let a = initRandomBigInt(nBitsA, Bits) + let b = initRandomBigInt(nBitsB, Bits) + let c = initRandomBigInt(nBitsC, Bits) + doAssert gcd(a, b) == gcd(b, a) + doAssert gcd(a, zero) == a + doAssert gcd(a, a) == a + doAssert gcd(c * a, c * b) == c * gcd(a,b) + doAssert gcd(a, gcd(b, c)) == gcd(gcd(a, b), c) + doAssert gcd(a, b) == gcd(b, a mod b) + main() From 3fe120e4deb41a3fc2965a8174f1d5b70315df43 Mon Sep 17 00:00:00 2001 From: Dimitri Lesnoff Date: Sun, 14 Aug 2022 10:54:03 +0200 Subject: [PATCH 8/8] Benchmark random, multiplication and division of bigints of up to 10_000 limbs --- benchmarks/benchBigints.nim | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 benchmarks/benchBigints.nim diff --git a/benchmarks/benchBigints.nim b/benchmarks/benchBigints.nim new file mode 100644 index 0000000..f5b82da --- /dev/null +++ b/benchmarks/benchBigints.nim @@ -0,0 +1,61 @@ +import bigints +import benchy +import random + +block: # Bench random generation + randomize() + var n = 100 + timeIt "bench Random generation of bigints with 100 limbs": + keep initRandomBigint(n) + timeIt "bench Random generation of bigints with 1_000 limbs": + keep initRandomBigint(n) + n = 5_000 + timeIt "bench Random generation of bigints with 5_000 limbs": + keep initRandomBigint(n) + n = 10_000 + timeIt "bench Random generation of bigints with 10_000 limbs": + keep initRandomBigint(n) +block: # Bench multiplication + randomize() + var n = 100 + timeIt "bench Multiplication of bigints with 100 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a*b + n = 1_000 + timeIt "bench Multiplication of bigints with 1_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a*b + n = 5_000 + timeIt "bench Multiplication of bigints with 5_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a*b + n = 10_000 + timeIt "bench Multiplication of bigints with 10_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a*b +block: # Bench division + randomize() + var n = 100 + timeIt "bench Division of bigints with 100 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a div b + n = 1_000 + timeIt "bench Division of bigints with 1_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a div b + n = 5_000 + timeIt "bench Division of bigints with 5_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a div b + n = 10_000 + timeIt "bench Division of bigints with 10_000 limbs": + var a: Bigint = initRandomBigint(n) + var b: Bigint = initRandomBigint(n) + keep a div b