Skip to content

Add modular exponentiation and modular inverse, closes #45 #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jan 22, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3d5b522
Add modular exponentiation
dlesnoff Jan 5, 2022
daec81c
Add modular inverse to complete modular exponentiation
dlesnoff Jan 6, 2022
7235a40
Fix indentation and docstring for modular inverse
dlesnoff Jan 8, 2022
63979d6
Test if integer is invertible modulo composite modulus
dlesnoff Jan 8, 2022
11c08ea
Changed to if..elif..else construct and add tests for zero
dlesnoff Jan 10, 2022
14bbd54
Merge branch 'master' into devel
narimiran Jan 10, 2022
9f8811d
Optimize loop checks in powmod
dlesnoff Jan 10, 2022
2eb864c
Update docstring of invmod
dlesnoff Jan 10, 2022
c20bcf6
Add runnableExamples and some recommandations
dlesnoff Jan 12, 2022
4428d18
Rename variables invmod
dlesnoff Jan 12, 2022
6e9ddd1
Add tests for negative integers for invmod
dlesnoff Jan 12, 2022
010845b
Apply suggestions from code review
dlesnoff Jan 13, 2022
1a0dc03
Avoid a function call if exponent is negative
dlesnoff Jan 13, 2022
ceb2f35
Remove ternary operators
dlesnoff Jan 13, 2022
60c6a6d
Fix <0 comparison test
dlesnoff Jan 13, 2022
2412bce
Add modular exponentiation
dlesnoff Jan 5, 2022
189b061
Rebase tests
dlesnoff Jan 16, 2022
e3a3823
Fix indentation and docstring for modular inverse
dlesnoff Jan 8, 2022
e52effc
Test if integer is invertible modulo composite modulus
dlesnoff Jan 8, 2022
8cced8a
Changed to if..elif..else construct and add tests for zero
dlesnoff Jan 10, 2022
4bbfa5b
Optimize loop checks in powmod
dlesnoff Jan 10, 2022
6bff716
Update docstring of invmod
dlesnoff Jan 10, 2022
961466f
Add runnableExamples and some recommandations
dlesnoff Jan 12, 2022
a07a52a
Apply suggestions from code review
dlesnoff Jan 13, 2022
8371690
Avoid a function call if exponent is negative
dlesnoff Jan 13, 2022
cc1ba71
Remove ternary operators
dlesnoff Jan 13, 2022
c4a4710
Fix <0 comparison test
dlesnoff Jan 13, 2022
da464b1
remove a merge conflict
dlesnoff Jan 16, 2022
4d4871d
Merge original branch with rebased local branch
dlesnoff Jan 16, 2022
c057c6e
Remove modexp example
dlesnoff Jan 19, 2022
65c5766
Add more tests for invmod
dlesnoff Jan 20, 2022
fa0f002
Merge branch 'master' into devel
dlesnoff Jan 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/bigints.nim
Original file line number Diff line number Diff line change
Expand Up @@ -956,3 +956,64 @@ iterator `..<`*(a, b: BigInt): BigInt =
while res < b:
yield res
inc res

func invmod*(a, modulus: BigInt): BigInt =
## Compute the modular inverse of `a` modulo `modulus`.
## The return value is always in the range `[1, modulus-1]`
runnableExamples:
invmod(3.initBigInt, 7.initBigInt) = 5.initBigInt

# extended Euclidean algorithm
if modulus < 0:
raise newException(ValueError, "modulus must be strictly positive")
elif modulus.isZero:
raise newException(DivByZeroDefect, "modulus must be nonzero")
elif a == 0:
raise newException(DivByZeroDefect, "0 has no modular inverse")
else:
var
r0 = ((a mod modulus) + modulus) mod modulus
r1 = modulus
s0 = one
s1 = zero
while r1 > 0:
let
q = r0 div r1
rk = r0 - q * r1
sk = s0 - q * s1
r0 = r1
r1 = rk
s0 = s1
s1 = sk
if r0 != one:
raise newException(ValueError, $a & " has no modular inverse modulo " & $modulus)
result = ((s0 mod modulus) + modulus) mod modulus

func powmod*(base, exponent, modulus: BigInt): BigInt =
## Compute modular exponentation of `base` with power `exponent` modulo `modulus`.
## The return value is always in the range `[0, modulus-1]`.
runnableExamples:
let two = 2.initBigInt
let three = 3.initBigInt
let seven = 7.initBigInt
assert powmod(two, three, seven) == one
if modulus < 0:
raise newException(ValueError, "modulus must be strictly positive")
elif modulus.isZero:
raise newException(DivByZeroDefect, "modulus must be nonzero")
elif modulus == 1:
return zero
elif exponent < 0:
let baseInv = invmod(base, modulus)
return powmod(baseInv, -exponent, modulus)
else:
var
exp = exponent
basePow = ((base mod modulus) + modulus) mod modulus # Base stays in [0, m-1]
result = one
while not exp.isZero:
if (exp.limbs[0] and 1) != 0:
result = (result * basePow) mod modulus
basePow = (basePow * basePow) mod modulus
exp = exp shr 1

65 changes: 65 additions & 0 deletions tests/tbigints.nim
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,75 @@ template main() =
doAssert pow(zero, 0) == one
doAssert pow(zero, 1) == zero

block: # invmod
# with prime modulus
let a = "30292868".initBigInt
let b = "48810860".initBigInt
let p = "60449131".initBigInt # p is prime
doAssert invmod(a, p) == "51713091".initBigInt
doAssert invmod(-b, p) == "31975542".initBigInt
# with composite modulus
let c = "2472018".initBigInt
let n = "3917515".initBigInt # 5 * 7 * 19 * 43 * 137
let d = "1831482".initBigInt
doAssert invmod(c, n) == "2622632".initBigInt
doAssert invmod(one, n) == one
doAssert invmod(n-one, n) == n-one
doAssert invmod(-d, n) == "2502552".initBigInt
doAssertRaises(DivByZeroDefect): discard invmod(zero, n)
doAssertRaises(DivByZeroDefect): discard invmod(one, zero)
doAssertRaises(ValueError): discard invmod(one, -7.initBigInt)
doAssertRaises(ValueError): discard invmod(3.initBigInt, 18.initBigInt) # 3 is not invertible since gcd(3, 18) = 3 != 1

block: # powmod
let a = "30292868".initBigInt
let p = "60449131".initBigInt # p is prime
let two = 2.initBigInt
doAssert powmod(a, two, p) == "25760702".initBigInt
# Fermat's little theorem: a^p ≡ a mod p
doAssert powmod(a, p, p) == a
# Euler's identity a^(p-1) \equiv 1 \bmod p
doAssert powmod(a, p - one, p) == one
# We can invert a using Euler's identity / Fermat's little theorem
doAssert powmod(a, p - two, p) == "51713091".initBigInt
# We can reduce the exponent modulo phi(p) = p - 1, since p is prime
doAssert powmod(a, 2.initBigInt*p, p) == (a * a mod p)

let p2 = 761.initBigInt
var a2 = 1.initBigInt
# Fermat's little theorem: a^p ≡ a mod p
while a2 < p2:
doAssert powmod(a2, p2, p2) == a2
a2.inc

block: # Composite modulus
let a = "2472018".initBigInt
let n = "3917515".initBigInt # 5 * 7 * 19 * 43 * 137
let euler_phi = "2467584".initBigInt
doAssert powmod(a, 52.initBigInt, n) == "2305846".initBigInt
doAssert powmod(a, euler_phi, n) == one
# Edge cases
doAssert powmod(a, one, n) == a
doAssert powmod(a, zero, n) == one
doAssert powmod(zero, zero, n) == one
doAssert powmod(zero, one, n) == zero

block: # powmod with negative base
let a = "1986599".initBigInt
let p = "10230581".initBigInt
doAssert powmod(-a, 2.initBigInt, p) == "6199079".initBigInt

block: # powmod with negative exponent
let a = "1912".initBigInt
let p = "5297".initBigInt
doAssert powmod(a, -1.initBigInt, p) == "1460".initBigInt
doAssert powmod(a, one-p, p) == one

block: # div/mod
doAssertRaises(DivByZeroDefect): discard one div zero
doAssertRaises(DivByZeroDefect): discard one mod zero
doAssertRaises(DivByZeroDefect): discard divmod(one, zero)


static: main()
main()