I\'m looking for a better algorithm than one I found on stackoverflow to handle 4096 byte numbers, i\'m hitting a maximum recursion depth.
Code from stackoverlow post, i
Suppose that for some reason the linear congruence equations you'll be 'attacking' come up 'empty' (no solutions) often enough to be a design criteria for your algorithm.
It turns out that you can use just (with any real overhead) the residue operations to answer that binary question -
There exist solutions XOR There are no solutions
This might have utility in cryptography; see also the abstract,
Introduction of the Residue Number Arithmetic Logic Unit
With Brief Computational Complexity Analysis
Once you determine that a solution exist, you can use back substitution
and the ALU to determine a solution.
Also, you'll have calculated the gcd(a,m) and can construct the coefficients of Bézout's identity
(if you need them).
Following is python program that incorporates the above ideas; it calculates the minimal solution when it exists and prints out Bézout's identity.
test_data = [ \
(32,12,82), \
(9,3,23), \
(17,41,73), \
(227,1,2011), \
(25,15,29), \
(2,22,71), \
(7,10,21), \
(124,58,900), \
(46, 12, 240), \
]
for lc in test_data:
LC = lc
back_sub_List = []
while True:
back_sub_List.append(LC)
n_mod_a = LC[2] % LC[0]
if n_mod_a == 0:
break
LC = (n_mod_a, -LC[1] % LC[0], LC[0])
gcd_of_a0_n0 = LC[0]
if LC[1] % LC[0] != 0:
print(f"No solution for {back_sub_List[0][0]}x = {back_sub_List[0][1]} (mod {back_sub_List[0][2]})")
else:
k = 0
for LC in back_sub_List[::-1]: # solve with back substitution
a,b,m = LC
k = (b + k*m) // a # optimize calculation since the remainder is zero?
print(f"The minimal solution for {back_sub_List[0][0]}x = {back_sub_List[0][1]} (mod {back_sub_List[0][2]}) is equal to {k}")
# get bezout
S = [1,0]
T = [0,1]
for LC in back_sub_List:
a,b,n = LC
q = n // a
s = S[0] - q * S[1]
S = [S[1], s]
t = T[0] - q * T[1]
T = [T[1], t]
print(f" Bézout's identity: ({S[0]})({lc[2]}) + ({T[0]})({lc[0]}) = {gcd_of_a0_n0}")
PROGRAM OUTPUT
The minimal solution for 32x = 12 (mod 82) is equal to 26
Bézout's identity: (-7)(82) + (18)(32) = 2
The minimal solution for 9x = 3 (mod 23) is equal to 8
Bézout's identity: (2)(23) + (-5)(9) = 1
The minimal solution for 17x = 41 (mod 73) is equal to 11
Bézout's identity: (7)(73) + (-30)(17) = 1
The minimal solution for 227x = 1 (mod 2011) is equal to 1320
Bézout's identity: (78)(2011) + (-691)(227) = 1
The minimal solution for 25x = 15 (mod 29) is equal to 18
Bézout's identity: (-6)(29) + (7)(25) = 1
The minimal solution for 2x = 22 (mod 71) is equal to 11
Bézout's identity: (1)(71) + (-35)(2) = 1
No solution for 7x = 10 (mod 21)
Bézout's identity: (0)(21) + (1)(7) = 7
No solution for 124x = 58 (mod 900)
Bézout's identity: (4)(900) + (-29)(124) = 4
The minimal solution for 46x = 12 (mod 240) is equal to 42
Bézout's identity: (-9)(240) + (47)(46) = 2
If you have Python 3.8 or later, you can do everything you need to with a very small number of lines of code.
First some mathematics: I'm assuming that you want to solve ax = b (mod m)
for an integer x
, given integers a
, b
and m
. I'm also assuming that m
is positive.
The first thing you need to compute is the greatest common divisor g
of a
and m
. There are two cases:
if b
is not a multiple of g
, then the congruence has no solutions (if ax + my = b
for some integers x
and y
, then any common divisor of a
and m
must also be a divisor of b
)
if b
is a multiple of g
, then the congruence is exactly equivalent to (a/g)x = (b/g) (mod (m/g))
. Now a/g
and m/g
are relatively prime, so we can compute an inverse to a/g
modulo m/g
. Multiplying that inverse by b/g
gives a solution, and the general solution can be obtained by adding an arbitrary multiple of m/g
to that solution.
Python's math module has had a gcd function since Python 3.5, and the built-in pow function can be used to compute modular inverses since Python 3.8.
Putting it all together, here's some code. First a function that finds the general solution, or raises an exception if no solution exists. If it succeeds, it returns two integers. The first gives a particular solution; the second gives the modulus that provides the general solution.
def solve_linear_congruence(a, b, m):
""" Describe all solutions to ax = b (mod m), or raise ValueError. """
g = math.gcd(a, m)
if b % g:
raise ValueError("No solutions")
a, b, m = a//g, b//g, m//g
return pow(a, -1, m) * b % m, m
And then some driver code, to demonstrate how to use the above.
def print_solutions(a, b, m):
print(f"Solving the congruence: {a}x = {b} (mod {m})")
try:
x, mx = solve_linear_congruence(a, b, m)
except ValueError:
print("No solutions")
else:
print(f"Particular solution: x = {x}")
print(f"General solution: x = {x} (mod {mx})")
Example use:
>>> print_solutions(272, 256, 1009)
Solving the congruence: 272x = 256 (mod 1009)
Particular solution: x = 179
General solution: x = 179 (mod 1009)
>>> print_solutions(98, 105, 1001)
Solving the congruence: 98x = 105 (mod 1001)
Particular solution: x = 93
General solution: x = 93 (mod 143)
>>> print_solutions(98, 107, 1001)
Solving the congruence: 98x = 107 (mod 1001)
No solutions