Python'da modüler çarpımsal ters fonksiyon


112

Bazı standart Python modülleri , bir sayının modüler çarpımsal tersini hesaplamak için bir işlev içerir mi , yani y = invmod(x, p)böyle bir sayı x*y == 1 (mod p)? Google bu konuda iyi bir ipucu vermiyor gibi görünüyor.

Elbette, ev yapımı 10 satırlık genişletilmiş Öklid algoritması ortaya çıkabilir , ancak neden tekerleği yeniden icat etsin?

Örneğin, Java en BigIntegerbulunur modInverseyöntemi. Python'da benzer bir şey yok mu?


18
Python 3.8 olarak (nedeniyle bu yıl tahliye edilecek), kullanmak mümkün olacak yerleşik powbunun için işlevi: y = pow(x, -1, p). Bugs.python.org/issue36027 adresine bakın . Sorunun sorulmasından standart kitaplıkta görünen bir çözüme kadar sadece 8,5 yıl sürdü!
Mark Dickinson

4
@MarkDickinson'ın bu çok faydalı geliştirmenin yazarı olduğunu söylemeyi mütevazı bir şekilde ihmal ettiğini görüyorum, bu yüzden yapacağım. Bu iş için teşekkürler Mark, harika görünüyor!
Don Hatch

Yanıtlar:


129

Belki birisi bunu yararlı bulabilir ( wikibook'lardan ):

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
Bu algoritmayı kullanarak negatif sayılarla ilgili sorunlar yaşıyordum. modinv (-3, 11) çalışmadı. Egcd'yi bu pdf'nin ikinci sayfasındaki uygulamayla değiştirerek düzelttim: anh.cs.luc.edu/331/notes/xgcd.pdf Umarım yardımcı olur!
Qaz

@Qaz Pozitif yapmak için -3 modulo 11'i de azaltabilirsiniz, bu durumda modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Muhtemelen PDF'nizdeki algoritmanın bir noktada yaptığı şey budur.
Thomas

1
Eğer kullanıyorsan sympy, o x, _, g = sympy.numbers.igcdex(a, m)zaman hile yapar.
Lynn

59

Modülünüz asal ise (siz onu çağırırsınız p), o zaman basitçe hesaplayabilirsiniz:

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

Veya uygun Python'da:

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

Python'da bazı sayı teorisi yetenekleri uygulayan biri: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

İşte komut isteminde yapılan bir örnek:

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

1
Naif üs alma, 1000000007 gibi makul derecede büyük herhangi bir p değeri için zaman (ve bellek) sınırı nedeniyle bir seçenek değildir.
dorserg

16
modüler üs alma, en fazla N * 2 çarpımı ile yapılır, burada N, üs içindeki bit sayısıdır. 2 ** 63-1'lik bir modül kullanarak tersi komut isteminde hesaplanabilir ve hemen bir sonuç verir.
phkahler

3
Vay harika. Hızlı üs alma işleminin farkındayım, sadece pow () işlevinin üçüncü argümanı alıp modüler üs almaya dönüştürebileceğinin farkında değildim.
dorserg

5
Bu yüzden Python kullanıyorsunuz değil mi? Çünkü harika :-)
phkahler

2
Bu arada, bu işe yarar çünkü Fermat'tan küçük teorem kuvveti (x, m-1, m) 1 olmalıdır. Dolayısıyla (pow (x, m-2, m) * x)% m == 1. So pow (x, m-2, m), x'in (mod m) tersidir.
Piotr Dabkowski

21

Ayrıca gmpy modülüne de bakmak isteyebilirsiniz . Python ve GMP çok hassas kitaplığı arasında bir arayüzdür. gmpy, tam olarak ihtiyacınız olanı yapan bir ters çevirme işlevi sağlar:

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

Güncellenen cevap

@Hyh tarafından belirtildiği gibi gmpy.invert(), tersi yoksa 0 döndürür. Bu, GMP'nin mpz_invert()işlevinin davranışıyla eşleşir . gmpy.divm(a, b, m)genel bir çözüm sağlar 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çarpımsal tersi olmadığında bir çözüm döndürür ve bir istisna oluşturur.

Sorumluluk reddi: Gmpy kitaplığının şu anda bakıcısıyım.

Güncellenmiş cevap 2

gmpy2 artık tersi olmadığında düzgün bir şekilde bir istisna oluşturur:

>>> import gmpy2

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

Bu gmpy.invert(0,5) = mpz(0)bir hata ortaya
çıkarmak

@hyh Bunu gmpy'nin ana sayfasında bir sorun olarak bildirebilir misiniz? Sorunlar bildirilirse her zaman takdir edilir.
casevh

BTW, bu gmpypakette modüler bir çarpma var mı? (yani aynı değere sahip ancak (a * b)% p? den daha hızlı olan bazı işlevler )
h__

Daha önce önerilmişti ve farklı yöntemler deniyorum. Sadece (a * b) % pbir fonksiyonda hesaplamanın en basit yaklaşımı, (a * b) % pPython'da değerlendirme yapmaktan daha hızlı değildir . Bir işlev çağrısının ek yükü, ifadeyi değerlendirme maliyetinden daha büyüktür. Daha fazla ayrıntı için code.google.com/p/gmpy/issues/detail?id=61 adresine bakın.
casevh

2
Harika olan şey, bunun aynı zamanda asal olmayan modulii için de işe yaramasıdır.
synecdoche

14

3.8 pitondan itibaren pow () fonksiyonu bir modül ve bir negatif tamsayı alabilir. Buraya bakın . Nasıl kullanılacağına dair durumları

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


6

Sembolik matematik için bir python modülü olan Sympy , kendi modunuzu uygulamak istemiyorsanız (veya zaten Sympy kullanıyorsanız) yerleşik bir modüler ters fonksiyona sahiptir:

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'

Bu, Sympy web sitesinde belgelenmemiş gibi görünüyor, ancak işte belge: Github'da Sympy mod_inverse docstring


2

İşte kodum, özensiz olabilir ama yine de benim için çalışıyor gibi görünüyor.

# 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

Yukarıdaki kod python3'te çalışmaz ve GCD varyantlarına kıyasla daha az verimlidir. Ancak bu kod çok şeffaftır. Daha kompakt bir sürüm oluşturmam için beni tetikledi:

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

1
Bunu çocuklara ve ne zaman açıklamakta sorun yok n == 7. Ama bunun dışında bu "algoritmanın" eşdeğeriyle ilgili:for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

Burada herhangi bir harici kitaplık kullanmadan bunu yapan kısa bir 1 satır var.

# 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

Bunun gerçekten sadece egcd olduğunu ve yalnızca tek bir ilgi katsayısını döndürmek için düzenlendiğini unutmayın.


1

Modüler çarpımsal tersini bulmak için Genişletilmiş Öklid Algoritmasını şu şekilde kullanmanızı öneririm:

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

Bu kodda bir hata var gibi görünüyor: a = prevX - quotient * Xolmalı X = prevX - quotient * Xve geri dönmelidir prevX. FWIW, bu uygulama Qaz'ın Märt Bakhoff'un cevabına yapılan yorumdaki bağlantısına benzer .
PM 2Ring

1

Bu iş parçacığından farklı çözümler deniyorum ve sonunda bunu kullanıyorum:

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'da Modular_inverse


1
bu kod geçersiz. returnegcd'de yanlış bir şekilde yönlendirilmiş
ph4r05

0

Pekala, python'da bir fonksiyonum yok ama C'de kolayca python'a dönüştürebileceğim bir fonksiyonum var, aşağıdaki c fonksiyonunda genişletilmiş öklid algoritması ters modu hesaplamak için kullanılıyor.

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 Fonksiyonu

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

Yukarıdaki C fonksiyonuna referans , iki Göreceli Asal Sayının Modüler Çarpımsal Tersini bulmak için aşağıdaki C bağlantı programından alınmıştır.


0

cpython uygulama kaynak kodundan :

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

bu kodun üzerindeki yoruma göre, küçük negatif değerler döndürebilir, böylece potansiyel olarak negatif olup olmadığını kontrol edebilir ve b döndürmeden önce negatif olduğunda n ekleyebilirsiniz.


"böylece potansiyel olarak negatif olup olmadığını kontrol edebilir ve b döndürmeden önce negatif olduğunda n ekleyebilirsin". Maalesef bu noktada n 0'dır. (N'nin orijinal değerini kaydetmeniz ve kullanmanız gerekir.)
Don Hatch

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.