Skip to content
2 changes: 1 addition & 1 deletion bigints.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 2 additions & 0 deletions src/bigints.nim
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ else:
func initBigInt*(val: BigInt): BigInt =
result = val


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

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

There already is a trailing newline, isn't there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No there is not!
I added one, It makes the code fits better in the buffer.

67 changes: 67 additions & 0 deletions src/bigints/utilities.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import std/random
import ../bigints

const zero = initBigInt(0)
type
RandomMode* = enum
Limbs, Bits

proc randomizeBigInt(container: var seq[uint32], number: Natural, mode: RandomMode = Limbs) =
case mode
of Limbs:
if number == 0:
raise newException(ValueError, "A Bigint must have at least one limb !")
# result.limbs.setLen(number)
for i in 0 ..< number-1:
container[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)
container[number-1] = word

of Bits: # unit == Bits
if number == 0:
container = @[]
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
# mask2 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 ..< container.high:
container[i] = rand(uint32)
let word = rand(uint32)
container[container.high] = word and mask or mask2

proc initRandomBigInt*(number: Natural, mode: RandomMode = Limbs): BigInt =
## Initializes a `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.
## Generates only positive bigints.
var limbs: seq[uint32]
let
remainder = number mod 32
n_limbs = (if remainder == 0: number shr 5 else: number shr 5 + 1)
case mode
of Limbs:
limbs.setLen(number)
of Bits:
if number == 0:
return zero
let
remainder = number mod 32
len_limbs = (if remainder == 0: number shr 5 else: number shr 5 + 1)
limbs.setLen(len_limbs)
randomizeBigInt(limbs, number, mode)
result = initBigInt(limbs, false)

67 changes: 67 additions & 0 deletions tests/trandom.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import bigints
import ../src/bigints/utilities
import std/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 =
result = memSize
for _ in 1..ord(memSizeUnit):
result *= 1024

const
memLimit = computeLimit(memSize, memSizeUnit) # Number of bytes
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:
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, 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()