Skip to content
61 changes: 61 additions & 0 deletions benchmarks/benchBigints.nim
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions benchmarks/benchExamples.nim
Original file line number Diff line number Diff line change
@@ -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)

2 changes: 2 additions & 0 deletions benchmarks/nim.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
path = "$projectPath/../src"
path = "$projectPath/../examples"
17 changes: 17 additions & 0 deletions bigints.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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

6 changes: 2 additions & 4 deletions examples/elliptic.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ""
Expand All @@ -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()
47 changes: 24 additions & 23 deletions examples/pidigits.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
9 changes: 5 additions & 4 deletions examples/rc_combperm.nim
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
47 changes: 46 additions & 1 deletion src/bigints.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
##
## The bitwise operations behave as if negative numbers were represented in 2's complement.

import std/[algorithm, bitops, math, options]
import std/[algorithm, bitops, math, options, random]

type
BigInt* = object
Expand Down Expand Up @@ -74,6 +74,50 @@ else:
func initBigInt*(val: BigInt): BigInt =
result = val

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


const
zero = initBigInt(0)
one = initBigInt(1)
Expand Down Expand Up @@ -1304,3 +1348,4 @@ func powmod*(base, exponent, modulus: BigInt): BigInt =
result = (result * basePow) mod modulus
basePow = (basePow * basePow) mod modulus
exponent = exponent shr 1

8 changes: 7 additions & 1 deletion tests/tbigints.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import bigints
import std/options
import random

const
zero = initBigInt(0)
Expand Down Expand Up @@ -880,6 +881,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()
Comment on lines +884 to +890
Copy link
Contributor Author

@dlesnoff dlesnoff Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to comment out the static tests and randomized versions. In the future, I will need to remove these comments and let this file in its initial state.

main()
Loading