Stack Overflow Asked by oppressionslayer on November 27, 2021
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 copy/pasted it but lost the original link:
def linear_congruence(a, b, m):
if b == 0:
return 0
if a < 0:
a = -a
b = -b
b %= m
while a > m:
a -= m
return (m * linear_congruence(m, -b, a) + b) // a
This works fine for smaller numbers, for example:
In [167]: pow_mod(8261, 63, 4033)
63 1 8261 4033
31 195 1728 4033
15 2221 1564 4033
7 1231 2098 4033
3 1518 1601 4033
1 2452 2246 4033
0 2147 3266 4033
Out[167]: 2147
And the linear congruence works:
linear_congruence(8261, 3266, 4033):
2147
But i hit maximum recursion depth with larger numbers. Is there a better algorithm or non recursive algorithm of the linear_congruence algorithm i provided?
Based on Eric Postpischil’s remark, i wrote the pseudocode from the wikipedia entry and created a very fast linear congruence algorithm utilizing the method from here: http://gauss.math.luc.edu/greicius/Math201/Fall2012/Lectures/linear-congruences.article.pdf .
This works nicely on pows with a powers of 2-1, to get the answer. I’m looking into how offsetting from this changes the answer and hope to incorporate it to work for those answers as well, but for now, i have what i need since i’m working with powers of 2 -1 for y in pow(x, y, z):
def fastlinearcongruencex(powx, divmodx, N, withstats=False):
x, y, z = egcditerx(powx, N, withstats)
if x > 1:
powx//=x
divmodx//=x
N//=x
if withstats == True:
print(f"powx = {powx}, divmodx = {divmodx}, N = {N}")
x, y, z = egcditerx(powx, N)
if withstats == True:
print(f"x = {x}, y = {y}, z = {z}")
answer = (y*divmodx)%N
if withstats == True:
print(f"answer = {answer}")
return answer
def egcditerx(a, b, withstats=False):
s = 0
r = b
old_s = 1
old_r = a
while r!= 0:
quotient = old_r // r
old_r, r = r, old_r - quotient * r
old_s, s = s, old_s - quotient * s
if withstats == True:
print(f"quotient = {quotient}, old_r = {old_r}, r = {r}, old_s = {old_s}, s = {s}")
if b != 0:
bezout_t = quotient = (old_r - old_s * a) // b
if withstats == True:
print(f"bezout_t = {bezout_t}")
else:
bezout_t = 0
if withstats == True:
print("Bézout coefficients:", (old_s, bezout_t))
print("greatest common divisor:", old_r)
return old_r, old_s, bezout_t
It even works instantaneously on 4096 byte numbers which is great:
In [19036]: rpowxxxwithbitlength(1009,offset=0, withstats=True, withx=True, withbl=True)
63 1 272 1009
31 272 327 1009
15 152 984 1009
7 236 625 1009
3 186 142 1009
1 178 993 1009
0 179 256 1009
Out[19036]: (179, 256, True, 272)
In [19037]: fastlinearcongruencex(272,256,1009)
Out[19037]: 179
Thank you Eric for pointing out what this was, i wrote an extremely fast linear congruence algorithm utilizing egcd and the procedure from the pdf above. If any stackoverflowers need a fast algorithm, please point them to this one. I also learned that congruence is always maintained when the pow(x,y,z) has a y off of the powers of 2-1. I will look into this further to see if an offset change exists to keep the answers intact and will follow up in the future if found.
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
Answered by CopyPasteIt on November 27, 2021
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
Answered by Mark Dickinson on November 27, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP