Hàm nghịch đảo đa số mô-đun trong Python


110

Một số mô-đun Python tiêu chuẩn có chứa một hàm để tính toán nghịch đảo số nhân mô-đun của một số, tức là một số y = invmod(x, p)như vậy x*y == 1 (mod p)không? Google dường như không đưa ra bất kỳ gợi ý tốt nào về điều này.

Tất nhiên, người ta có thể nghĩ ra 10 lớp lót của thuật toán Euclidean mở rộng được pha chế tại nhà , nhưng tại sao lại phải phát minh lại bánh xe.

Ví dụ, Java's BigIntegermodInversephương thức. Python không có cái gì đó tương tự?


18
Trong Python 3.8 (do được phát hành vào cuối năm nay), bạn sẽ có thể sử dụng được xây dựng trong powchức năng cho việc này: y = pow(x, -1, p). Xem bug.python.org/issue36027 . Chỉ mất 8,5 năm kể từ khi câu hỏi được hỏi đến một giải pháp xuất hiện trong thư viện chuẩn!
Mark Dickinson

4
Tôi thấy @MarkDickinson đã bỏ qua một cách khiêm tốn khi đề cập rằng ey là tác giả của cải tiến rất hữu ích này, vì vậy tôi sẽ làm. Cảm ơn vì công việc này, Mark, nó trông rất tuyệt!
Don Hatch

Câu trả lời:


128

Có thể ai đó sẽ thấy điều này hữu ích (từ 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
Tôi đã gặp sự cố với các số âm khi sử dụng thuật toán này. modinv (-3, 11) không hoạt động. Tôi đã sửa nó bằng cách thay thế egcd bằng cách triển khai trên trang hai của pdf này: anh.cs.luc.edu/331/notes/xgcd.pdf Hy vọng điều đó sẽ hữu ích!
Qaz

@Qaz Bạn cũng có thể chỉ cần giảm -3 modulo 11 để làm cho nó dương, trong trường hợp này là modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Đó có thể là những gì thuật toán trong tệp PDF của bạn xảy ra tại một số điểm.
Thomas

1
Nếu bạn tình cờ sử dụng sympy, thì hãy x, _, g = sympy.numbers.igcdex(a, m)thực hiện thủ thuật.
Lynn

59

Nếu mô đun của bạn là số nguyên tố (bạn gọi nó p) thì bạn có thể chỉ cần tính:

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

Hoặc bằng Python thích hợp:

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

Đây là một người đã triển khai một số khả năng lý thuyết số trong Python: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Đây là một ví dụ được thực hiện tại dấu nhắc:

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

1
Lũy thừa ngây thơ không phải là một lựa chọn vì thời gian (và bộ nhớ) giới hạn đối với bất kỳ giá trị hợp lý lớn của p như nói 1000000007.
dorserg

16
lũy thừa mô-đun được thực hiện với nhiều nhất N * 2 phép nhân trong đó N là số bit trong số mũ. sử dụng mô-đun 2 ** 63-1, nghịch đảo có thể được tính toán tại dấu nhắc và trả về kết quả ngay lập tức.
phkahler

3
Wow, tuyệt vời. Tôi biết về phép tính lũy thừa nhanh, tôi chỉ không biết rằng hàm pow () có thể nhận đối số thứ ba để biến nó thành lũy thừa mô-đun.
dorserg

5
Đó là lý do tại sao bạn đang sử dụng Python phải không? Bởi vì nó là tuyệt vời :-)
phkahler

2
Bằng cách này, điều này hoạt động vì từ định lý nhỏ Fermat, pow (x, m-1, m) phải là 1. Do đó (pow (x, m-2, m) * x)% m == 1. Vì vậy, pow (x, m-2, m) là nghịch đảo của x (mod m).
Piotr Dabkowski

21

Bạn cũng có thể muốn nhìn vào mô-đun gmpy . Nó là một giao diện giữa Python và thư viện đa độ chính xác GMP. gmpy cung cấp một hàm đảo ngược thực hiện chính xác những gì bạn cần:

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

Cập nhật câu trả lời

Như đã lưu ý bởi @hyh, giá trị gmpy.invert()trả về 0 nếu nghịch đảo không tồn tại. Điều đó phù hợp với hành vi của mpz_invert()chức năng của GMP . gmpy.divm(a, b, m)cung cấp một giải pháp chung cho 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()sẽ trả về một nghiệm khi gcd(b,m) == 1và đưa ra một ngoại lệ khi nghịch đảo nhân không tồn tại.

Tuyên bố từ chối trách nhiệm: Tôi là người bảo trì hiện tại của thư viện gmpy.

Cập nhật câu trả lời 2

gmpy2 bây giờ nâng đúng một ngoại lệ khi nghịch đảo không tồn tại:

>>> import gmpy2

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

Đây là mát mẻ cho đến khi tôi tìm thấy gmpy.invert(0,5) = mpz(0)thay vì tăng một lỗi ...
h__

@hyh Bạn có thể báo cáo vấn đề này tại trang chủ của gmpy không? Nó luôn được đánh giá cao nếu các vấn đề được báo cáo.
casevh

BTW, có một phép nhân mô-đun trong gmpygói này không? (tức là một số hàm có cùng giá trị nhưng nhanh hơn (a * b)% p?)
h__

Nó đã được đề xuất trước đây và tôi đang thử nghiệm các phương pháp khác nhau. Cách tiếp cận đơn giản nhất của việc chỉ tính toán (a * b) % ptrong một hàm không nhanh hơn là chỉ đánh giá (a * b) % ptrong Python. Chi phí cho một lệnh gọi hàm lớn hơn chi phí đánh giá biểu thức. Xem code.google.com/p/gmpy/issues/detail?id=61 để biết thêm chi tiết.
casevh

2
Điều tuyệt vời là điều này cũng hoạt động đối với các modulii không phải nguyên tố.
synecdoche

13

Kể từ 3.8 pythons, hàm pow () có thể nhận một mô-đun và một số nguyên âm. Xem tại đây . Trường hợp của họ về cách sử dụng nó là

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

8

Đây là một lớp lót cho CodeFights ; nó là một trong những giải pháp ngắn nhất:

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]

Nó sẽ trả về -1nếu Akhông có nghịch đảo nhân n.

Sử dụng:

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

Giải pháp sử dụng Thuật toán Euclid mở rộng .


6

Sympy , một mô-đun python cho toán học biểu tượng, có một chức năng nghịch đảo mô-đun được tích hợp sẵn nếu bạn không muốn triển khai mô-đun của riêng mình (hoặc nếu bạn đang sử dụng 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'

Điều này dường như không được ghi lại trên trang web Sympy, nhưng đây là docstring: Sympy mod_inverse docstring trên Github


2

Đây là mã của tôi, nó có thể cẩu thả nhưng nó có vẻ phù hợp với tôi.

# 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

Đoạn mã trên sẽ không chạy trong python3 và kém hiệu quả hơn so với các biến thể GCD. Tuy nhiên, mã này rất minh bạch. Nó đã kích hoạt tôi tạo ra một phiên bản nhỏ gọn hơn:

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

1
Điều này là OK để giải thích nó cho trẻ em, và khi nào n == 7. Nhưng nếu không thì nó tương đương với "thuật toán" này:for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

Dưới đây là 1 lớp lót ngắn gọn thực hiện điều đó mà không cần sử dụng bất kỳ thư viện bên ngoài nào.

# 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

Lưu ý rằng đây thực sự chỉ là egcd, được sắp xếp hợp lý để chỉ trả về hệ số quan tâm duy nhất.


1

Để tìm ra nghịch đảo nhân mô-đun, tôi khuyên bạn nên sử dụng Thuật toán Euclid mở rộng như sau:

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

Có vẻ như có một lỗi trong mã này: a = prevX - quotient * Xnên có X = prevX - quotient * X, và nó sẽ trở lại prevX. FWIW, cách triển khai này tương tự như trong liên kết của Qaz trong nhận xét cho câu trả lời của Märt Bakhoff.
PM 2Ring

1

Tôi thử các giải pháp khác nhau từ chủ đề này và cuối cùng tôi sử dụng giải pháp này:

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

Modular_inverse trong Python


1
mã này không hợp lệ. returntrong egcd được indended trong một cách sai lầm
ph4r05

0

Chà, tôi không có một hàm trong python nhưng tôi có một hàm trong C mà bạn có thể dễ dàng chuyển đổi sang python, trong thuật toán euclidian mở rộng hàm c bên dưới được sử dụng để tính toán mod nghịch đảo.

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

Hàm 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

Tham chiếu đến hàm C ở trên được lấy từ liên kết sau chương trình C để tìm Nghịch đảo mô đun của hai số nguyên tố tương đối


0

từ mã nguồn triển khai 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")

Theo nhận xét ở trên, mã này có thể trả về các giá trị âm nhỏ, vì vậy bạn có thể kiểm tra xem có âm hay không và thêm n khi âm trước khi trả về b.


"để bạn có thể kiểm tra xem có âm hay không và thêm n khi âm trước khi trả về b". Thật không may, n là 0 tại thời điểm đó. (Bạn phải lưu và sử dụng giá trị ban đầu của n.)
Don Hatch

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.