Python中的模块化乘法逆函数


110

一些标准的Python模块是否包含用于计算数字(即诸如)的模数乘法逆的函数?Google似乎对此没有任何好的暗示。y = invmod(x, p)x*y == 1 (mod p)

当然,可以提出扩展的欧几里得算法的自酿10线性算法,但是为什么要重新发明轮子呢?

例如,Java的BigIntegerhas modInverse方法。Python没有类似的东西吗?


18
在Python 3.8(将于今年晚些时候发布)中,您将可以使用内置pow函数来实现此功能:y = pow(x, -1, p)。参见bugs.python.org/issue36027。从提出问题到在标准库中出现解决方案仅用了8.5年的时间!
Mark Dickinson

4
我看到@MarkDickinson谦虚地提到ey是这个非常有用的增强功能的作者,所以我会的。谢谢您的工作,马克,看起来很棒!
唐·哈奇

Answers:


128

也许有人会觉得这很有用(来自Wikibooks):

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

1
使用此算法时,我遇到了负数问题。modinv(-3,11)无效。我通过用此pdf 第二页上的实现替换egcd来解决此问题:anh.cs.luc.edu/331/notes/xgcd.pdf希望对您有所帮助!
2014年

@Qaz您也可以只降低-3模11以使其为正,在这种情况下,modinv(-3,11)== modinv(-3 + 11,11)== modinv(8,11)。这可能就是您的PDF中的算法在某个时候碰巧所做的。
Thomas

1
如果您碰巧正在使用sympy,那就x, _, g = sympy.numbers.igcdex(a, m)可以了。
林恩

59

如果您的模数是素数(称为p),则可以简单地计算:

y = x**(p-2) mod p  # Pseudocode

或者在Python中:

y = pow(x, p-2, p)

这是已经在Python中实现了一些数论功能的人:http : //www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

这是在提示符下完成的示例:

m = 1000000007
x = 1234567
y = pow(x,m-2,m)
y
989145189L
x*y
1221166008548163L
x*y % m
1L

1
天真幂是不是因为时间(记忆)为p的任何合理的大值就好说1000000007.一个选项限制
dorserg

16
模幂运算最多使用N * 2乘法完成,其中N是指数中的位数。使用2 ** 63-1的模数,可以在提示时计算逆,并立即返回结果。
phkahler 2011年

3
哇,酷极了。我知道快速取幂,只是我不知道pow()函数可以接受第三个参数,从而将其转换为模块化取幂。
dorserg 2011年

5
这就是为什么您使用Python对吗?因为它很棒:-)
phkahler 2011年

2
顺便说一句,因为在费马小定理pow(x,m-1,m)必须为1。因此(pow(x,m-2,m)* x)%m ==1。所以pow(x, m-2,m)是x(mod m)的倒数。
Piotr Dabkowski

21

您可能还需要查看gmpy模块。它是Python和GMP多重精度库之间的接口。gmpy提供了一个invert函数,可以完全满足您的需求:

>>> import gmpy
>>> gmpy.invert(1234567, 1000000007)
mpz(989145189)

更新的答案

如@hyh所示,gmpy.invert()如果不存在逆,则返回0。符合GMP mpz_invert()功能的行为。gmpy.divm(a, b, m)提供的一般解决方案a=bx (mod m)

>>> gmpy.divm(1, 1234567, 1000000007)
mpz(989145189)
>>> gmpy.divm(1, 0, 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: not invertible
>>> gmpy.divm(1, 4, 8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: not invertible
>>> gmpy.divm(1, 4, 9)
mpz(7)

divm()gcd(b,m) == 1不存在乘法逆时,将返回一个解决方案,并引发异常。

免责声明:我是gmpy库的当前维护者。

更新的答案2

现在,当反函数不存在时,gmpy2会正确引发一个异常:

>>> import gmpy2

>>> gmpy2.invert(0,5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: invert() no inverse exists

这很酷,直到我发现gmpy.invert(0,5) = mpz(0)而不是引发错误...
h__13年

@hyh您可以在gmpy的主页上报告此问题吗?如果报告了问题,我们将不胜感激。
casevh 2013年

顺便说一句,这个gmpy软件包中有模块化乘法吗?(即某些具有相同值但比(a * b)% p?更快的函数)
h__13年

之前已经提出过,我正在尝试不同的方法。仅(a * b) % p在函数中进行计算的最简单方法并不比仅(a * b) % p在Python中进行计算要快。函数调用的开销大于评估表达式的开销。有关更多详细信息,请参见code.google.com/p/gmpy/issues/detail?id=61
casevh

2
很棒的是,这也适用于非素数模数。
synecdoche

13

从3.8开始,python的pow()函数可以采用一个模数和一个负整数。看这里。他们如何使用它的情况是

>>> pow(38, -1, 97)
23
>>> 23 * 38 % 97 == 1
True

8

这是CodeFights的一线工具;它是最短的解决方案之一:

MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1]

-1如果A在中没有乘法逆,它将返回n

用法:

MMI(23, 99) # returns 56
MMI(18, 24) # return -1

该解决方案使用扩展的欧几里得算法


6

Sympy,为象征性的数学Python模块,具有内置式模块反函数,如果你不希望实现自己的(或者,如果您已经在使用Sympy):

from sympy import mod_inverse

mod_inverse(11, 35) # returns 16
mod_inverse(15, 35) # raises ValueError: 'inverse of 15 (mod 35) does not exist'

这似乎没有在Sympy网站上记录,但这里是文档字符串:Github上的Sympy mod_inverse文档字符串


2

这是我的代码,可能有点草率,但是无论如何它似乎对我有用。

# a is the number you want the inverse for
# b is the modulus

def mod_inverse(a, b):
    r = -1
    B = b
    A = a
    eq_set = []
    full_set = []
    mod_set = []

    #euclid's algorithm
    while r!=1 and r!=0:
        r = b%a
        q = b//a
        eq_set = [r, b, a, q*-1]
        b = a
        a = r
        full_set.append(eq_set)

    for i in range(0, 4):
        mod_set.append(full_set[-1][i])

    mod_set.insert(2, 1)
    counter = 0

    #extended euclid's algorithm
    for i in range(1, len(full_set)):
        if counter%2 == 0:
            mod_set[2] = full_set[-1*(i+1)][3]*mod_set[4]+mod_set[2]
            mod_set[3] = full_set[-1*(i+1)][1]

        elif counter%2 != 0:
            mod_set[4] = full_set[-1*(i+1)][3]*mod_set[2]+mod_set[4]
            mod_set[1] = full_set[-1*(i+1)][1]

        counter += 1

    if mod_set[3] == B:
        return mod_set[2]%B
    return mod_set[4]%B

2

上面的代码无法在python3中运行,并且与GCD变体相比效率较低。但是,此代码非常透明。它触发了我创建一个更紧凑的版本:

def imod(a, n):
 c = 1
 while (c % a > 0):
     c += n
 return c // a

1
可以向孩子以及何时解释n == 7。但除此之外,它等同于该“算法”:for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

这是一个简洁的1-liner,无需使用任何外部库即可执行此操作。

# Given 0<a<b, returns the unique c such that 0<c<b and a*c == gcd(a,b) (mod b).
# In particular, if a,b are relatively prime, returns the inverse of a modulo b.
def invmod(a,b): return 0 if a==0 else 1 if b%a==0 else b - invmod(b%a,a)*b//a

请注意,这实际上只是egcd,经过精简后仅返回单个感兴趣的系数。


1

为了弄清楚模块化乘法逆,我建议使用扩展欧几里得算法,如下所示:

def multiplicative_inverse(a, b):
    origA = a
    X = 0
    prevX = 1
    Y = 1
    prevY = 0
    while b != 0:
        temp = b
        quotient = a/b
        b = a%b
        a = temp
        temp = X
        a = prevX - quotient * X
        prevX = temp
        temp = Y
        Y = prevY - quotient * Y
        prevY = temp

    return origA + prevY

这段代码中似乎有一个错误:a = prevX - quotient * X应该是X = prevX - quotient * X,并且应该返回prevX。FWIW,此实现类似于Qaz在MärtBakhoff的回答的评论中的链接中的实现。
下午15年

1

我从该线程尝试了不同的解决方案,最后我使用了一个:

def egcd(a, b):
    lastremainder, remainder = abs(a), abs(b)
    x, lastx, y, lasty = 0, 1, 1, 0
    while remainder:
        lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
        x, lastx = lastx - quotient*x, x
        y, lasty = lasty - quotient*y, y
    return lastremainder, lastx * (-1 if a < 0 else 1), lasty * (-1 if b < 0 else 1)


def modinv(a, m):
    g, x, y = self.egcd(a, m)
    if g != 1:
        raise ValueError('modinv for {} does not exist'.format(a))
    return x % m

Python中的Modular_inverse


1
该代码无效。return在egcd以错误的方式indended
ph4r05

0

好吧,我在python中没有函数,但是我在C中具有可以轻松转换为python的函数,在下面的c函数中,扩展的欧几里得算法用于计算逆mod。

int imod(int a,int n){
int c,i=1;
while(1){
    c = n * i + 1;
    if(c%a==0){
        c = c/a;
        break;
    }
    i++;
}
return c;}

Python函数

def imod(a,n):
  i=1
  while True:
    c = n * i + 1;
    if(c%a==0):
      c = c/a
      break;
    i = i+1

  return c

从下面的链接C程序中引用了上面的C函数,以找到两个相对质数的模乘逆


0

从cpython实现源代码

def invmod(a, n):
    b, c = 1, 0
    while n:
        q, r = divmod(a, n)
        a, b, c, n = n, c, b - q*c, r
    # at this point a is the gcd of the original inputs
    if a == 1:
        return b
    raise ValueError("Not invertible")

根据此代码上方的注释,它可以返回小的负值,因此您可以潜在地检查是否为负,并在返回b之前将n添加为负。


“因此,您可以检查是否为负数,并在返回b之前在负数时添加n”。不幸的是,此时n为0。(您必须保存和使用n的原始值。)
Don Hatch

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.