【问题标题】:Solving modular linear congruences for large numbers求解大数的模线性同余
【发布时间】:2020-07-21 19:32:48
【问题描述】:

我正在寻找一种比我在 stackoverflow 上找到的更好的算法来处理 4096 字节数,我正在达到最大递归深度。

来自 stackoverlow 帖子的代码,我复制/粘贴它但丢失了原始链接:

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

这适用于较小的数字,例如:

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

但是我用更大的数字达到了最大递归深度。我提供的linear_congruence算法有更好的算法还是非递归算法?

根据 Eric Postpischil 的评论,我从维基百科条目中编写了伪代码,并利用此处的方法创建了一个非常快速的线性同余算法:http://gauss.math.luc.edu/greicius/Math201/Fall2012/Lectures/linear-congruences.article.pdf

这很适用于幂为 2-1 的 pow,以获得答案。我正在研究如何抵消这会改变答案,并希望将其纳入这些答案,但现在,我有我需要的东西,因为我正在使用 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

它甚至可以立即处理 4096 字节的数字,这很棒:

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

感谢 Eric 指出这是什么,我使用 egcd 和上面 pdf 中的过程编写了一个非常快速的线性同余算法。如果任何 stackoverflowers 需要快速算法,请将它们指向这个。我还了解到,当 pow(x,y,z) 的 y 偏离 2-1 的幂时,始终保持一致。我会进一步调查,看看是否存在偏移更改以保持答案完整,如果找到,我会在未来跟进。

【问题讨论】:

  • 你提前知道am互质吗?如果没有,您希望如何处理涉及多个解决方案或没有解决方案的案例?
  • 如果我能得到多种解决方案,那就太好了,我注意到它不是 100%,所以我很高兴听到有多种解决方案。我只将它用于 pow mod 的数字,所以我还没有看到没有解决方案的情况,所以我不确定我想如何处理这些,也许只是没有答案。现在,如果我没有得到正确的答案,我会做上面的下一个数字,通常会奏效。
  • 另外,我不知道 a 与 m 互质。我正在使用算法来生成 a,并从事个人教育数学项目,看看我是否可以制定可以预测最后一个模数 x 的数字。我知道,这听起来不可能,但这是一个有趣的项目,我在做一些听起来不可能的事情时很开心:-)
  • 将代码重写为循环,而不是递归调用。这是Extended Euclidean Algorithm,不应使用递归编写。
  • 来源很可能是this answer

标签: python math linear-programming modulus mod


【解决方案1】:

如果您拥有 Python 3.8 或更高版本,则只需很少的代码行即可完成所需的一切。

首先进行一些数学运算:我假设您想求解ax = b (mod m) 的整数x,给定整数abm。我还假设m 是肯定的。

您需要计算的第一件事是g 的最大公约数am。有两种情况:

  • 如果b不是g的倍数,则同余没有解(如果ax + my = b对于某些整数xy,那么am的任何公约数也必须是b的除数)

  • 如果b g 的倍数,那么全等就等于(a/g)x = (b/g) (mod (m/g))。现在a/gm/g 是互质的,所以我们可以计算出a/gm/g 的逆。乘以 b/g 的逆得到一个解,通过将m/g 的任意倍数添加到该解可以得到通解。

Python 的 math 模块自 Python 3.5 起就有一个 gcd 函数,而内置的 pow 函数可用于从 Python 3.8 起计算模逆。

总而言之,这里有一些代码。首先是一个找到通用解决方案的函数,或者如果不存在解决方案则引发异常。如果成功,则返回两个整数。第一个给出了一个特定的解决方案;第二个给出提供一般解的模数。

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

然后是一些驱动代码,来演示如何使用上面的。

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})")

使用示例:

>>> 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

【讨论】:

  • 谢谢!因为我没有 3.8,所以我在 VM 中进行了尝试,但这是一个很棒的补充。这很好用
【解决方案2】:

假设由于某种原因,您将要“攻击”的线性同余方程经常出现“空”(无解),足以成为您算法的设计标准。

事实证明,您可以只使用(有任何实际开销)残差运算来回答这个二元问题 -

存在解决方案 XOR 没有解决方案

这可能在密码学中有用;另见abstract

余数算术逻辑单元介绍
简单的计算复杂度分析

一旦确定存在解决方案,就可以使用反向替换
和 ALU 来确定解决方案。

此外,您将计算 gcd(a,m) 并可以构造 Bézout 恒等式的系数
如果你需要它们)。

以下是结合上述思想的python程序;它计算存在的最小解并打印出 Bézout 的身份。

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}")

程序输出

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

【讨论】:

  • 我已经认识到要计算 Bézout 的系数,计算机算法应该使用精确的整数除法。兴趣:1993 年 Tudor Jebelean 写了两篇论文 (1) A Generalization of the Binary GCD Algorithm (2) An Algorithm for Exact Division
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多