Modulare multiplikative Inversfunktion in Python

110

Enthält ein Standard-Python-Modul eine Funktion zum Berechnen der modularen multiplikativen Inversen einer Zahl, dh einer y = invmod(x, p)solchen Zahl x*y == 1 (mod p)? Google scheint hierzu keine guten Hinweise zu geben.

Natürlich kann man sich einen selbst gebrauten 10-Liner mit erweitertem euklidischen Algorithmus einfallen lassen , aber warum das Rad neu erfinden?

Zum Beispiel hat Java BigIntegereine modInverseMethode. Hat Python nicht etwas Ähnliches?

dorserg
quelle
18
In Python 3.8 (wird voraussichtlich noch in diesem Jahr veröffentlicht) können Sie die integrierte powFunktion für Folgendes verwenden : y = pow(x, -1, p). Siehe bugs.python.org/issue36027 . Es dauerte nur 8,5 Jahre von der Frage bis zu einer Lösung in der Standardbibliothek!
Mark Dickinson
4
Ich sehe @MarkDickinson bescheiden vernachlässigt zu erwähnen, dass ey der Autor dieser sehr nützlichen Verbesserung ist, also werde ich es tun. Danke für diese Arbeit, Mark, sie sieht toll aus!
Don Hatch

Antworten:

127

Vielleicht findet jemand dies nützlich (aus 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
Märt Bakhoff
quelle
1
Ich hatte Probleme mit negativen Zahlen mit diesem Algorithmus. modinv (-3, 11) hat nicht funktioniert. Ich habe es behoben, indem ich egcd durch die Implementierung auf Seite zwei dieses PDFs ersetzt habe : anh.cs.luc.edu/331/notes/xgcd.pdf Hoffe, das hilft!
Qaz
@Qaz Sie können auch einfach -3 Modulo 11 reduzieren, um es positiv zu machen, in diesem Fall modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Das ist wahrscheinlich das, was der Algorithmus in Ihrer PDF-Datei irgendwann tut.
Thomas
1
Wenn Sie gerade verwenden sympy, dann x, _, g = sympy.numbers.igcdex(a, m)macht der Trick.
Lynn
59

Wenn Ihr Modul prim ist (Sie nennen es p), können Sie einfach berechnen:

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

Oder in Python:

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

Hier ist jemand, der einige Funktionen der Zahlentheorie in Python implementiert hat: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Hier ist ein Beispiel an der Eingabeaufforderung:

m = 1000000007
x = 1234567
y = pow(x,m-2,m)
y
989145189L
x*y
1221166008548163L
x*y % m
1L
Phkahler
quelle
1
Naive Potenzierung ist keine Option, da die Zeit (und das Gedächtnis) für einen vernünftig großen Wert von p wie beispielsweise 1000000007 begrenzt sind.
dorserg
16
Die modulare Exponentiation erfolgt mit höchstens N * 2 Multiplikationen, wobei N die Anzahl der Bits im Exponenten ist. Mit einem Modul von 2 ** 63-1 kann die Inverse an der Eingabeaufforderung berechnet werden und gibt sofort ein Ergebnis zurück.
Phkahler
3
Wow cool. Mir ist eine schnelle Potenzierung bewusst. Ich war mir nur nicht bewusst, dass die Funktion pow () ein drittes Argument annehmen kann, das sie in eine modulare Potenzierung umwandelt.
Dorserg
5
Deshalb verwenden Sie Python richtig? Weil es großartig ist :-)
Phkahler
2
Übrigens funktioniert das, weil nach Fermat der kleine Satz pow (x, m-1, m) 1 sein muss. Daher (pow (x, m-2, m) * x)% m == 1. Also pow (x, m-2, m) ist die Umkehrung von x (mod m).
Piotr Dabkowski
21

Vielleicht möchten Sie sich auch das gmpy- Modul ansehen . Es ist eine Schnittstelle zwischen Python und der GMP-Bibliothek mit mehrfacher Genauigkeit. gmpy bietet eine Invertierungsfunktion, die genau das tut, was Sie brauchen:

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

Antwort aktualisiert

Wie von @hyh angegeben, gmpy.invert()gibt die 0 zurück, wenn die Umkehrung nicht existiert. Das entspricht dem Verhalten der GMP- mpz_invert()Funktion. gmpy.divm(a, b, m)bietet eine allgemeine Lösung für 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()gibt eine Lösung zurück, wenn gcd(b,m) == 1und löst eine Ausnahme aus, wenn die multiplikative Inverse nicht existiert.

Haftungsausschluss: Ich bin der aktuelle Betreuer der gmpy-Bibliothek.

Aktualisierte Antwort 2

gmpy2 löst jetzt ordnungsgemäß eine Ausnahme aus, wenn die Umkehrung nicht vorhanden ist:

>>> import gmpy2

>>> gmpy2.invert(0,5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: invert() no inverse exists
casevh
quelle
Das ist cool, bis ich gefunden habe, gmpy.invert(0,5) = mpz(0)anstatt einen Fehler
auszulösen
@hyh Kannst du dies als Problem auf der Homepage von gmpy melden? Es wird immer geschätzt, wenn Probleme gemeldet werden.
casevh
Übrigens, gibt es eine modulare Multiplikation in diesem gmpyPaket? (dh eine Funktion, die den gleichen Wert hat, aber schneller als (a * b)% p?)
h__
Es wurde bereits vorgeschlagen und ich experimentiere mit verschiedenen Methoden. Der einfachste Ansatz, nur (a * b) % pin einer Funktion zu berechnen, ist nicht schneller als nur (a * b) % pin Python auszuwerten . Der Aufwand für einen Funktionsaufruf ist höher als die Kosten für die Auswertung des Ausdrucks. Weitere Informationen finden Sie unter code.google.com/p/gmpy/issues/detail?id=61 .
casevh
2
Das Tolle ist, dass dies auch für Nicht-Prim-Module funktioniert.
Synecdoche
13

Ab 3.8 Python kann die pow () -Funktion einen Modul und eine negative ganze Zahl annehmen. Siehe hier . Ihr Fall für die Verwendung ist

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

Hier ist ein Einzeiler für CodeFights ; Es ist eine der kürzesten Lösungen:

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]

Es wird zurückgegeben, -1wenn Akeine multiplikative Inverse in vorhanden ist n.

Verwendung:

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

Die Lösung verwendet den erweiterten euklidischen Algorithmus .

HKTonyLee
quelle
6

Sympy , ein Python-Modul für symbolische Mathematik, verfügt über eine integrierte modulare Umkehrfunktion, wenn Sie keine eigene implementieren möchten (oder wenn Sie Sympy bereits verwenden):

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'

Dies scheint nicht auf der Sympy-Website dokumentiert zu sein, aber hier ist die Dokumentzeichenfolge: Sympy mod_inverse docstring auf Github

Chris Chudzicki
quelle
2

Hier ist mein Code, er mag schlampig sein, aber er scheint trotzdem für mich zu funktionieren.

# 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
Eric
quelle
2

Der obige Code läuft nicht in Python3 und ist im Vergleich zu den GCD-Varianten weniger effizient. Dieser Code ist jedoch sehr transparent. Es hat mich veranlasst, eine kompaktere Version zu erstellen:

def imod(a, n):
 c = 1
 while (c % a > 0):
     c += n
 return c // a
BvdM
quelle
1
Dies ist in Ordnung, um es Kindern zu erklären, und wann n == 7. Aber ansonsten ist es ungefähr gleichwertig mit diesem "Algorithmus":for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor
2

Hier ist ein prägnanter 1-Liner, der dies ohne Verwendung externer Bibliotheken tut.

# 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

Beachten Sie, dass dies wirklich nur egcd ist und optimiert wurde, um nur den einzelnen interessierenden Koeffizienten zurückzugeben.

Don Hatch
quelle
1

Um die modulare multiplikative Inverse herauszufinden, empfehle ich die Verwendung des erweiterten euklidischen Algorithmus wie folgt:

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
David Sulpy
quelle
Es scheint einen Fehler in diesem Code zu geben: a = prevX - quotient * Xsollte sein X = prevX - quotient * X, und es sollte zurückkehren prevX. FWIW, diese Implementierung ähnelt der in Qaz 'Link im Kommentar zu Märt Bakhoffs Antwort.
PM 2Ring
1

Ich probiere verschiedene Lösungen aus diesem Thread aus und am Ende benutze ich diese:

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

Al Po
quelle
1
Dieser Code ist ungültig. returnin egcd ist falsch
eingerückt
0

Nun, ich habe keine Funktion in Python, aber ich habe eine Funktion in C, die Sie leicht in Python konvertieren können. In der folgenden c-Funktion wird der erweiterte Euklidian-Algorithmus verwendet, um den inversen Mod zu berechnen.

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

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

Der Verweis auf die obige C-Funktion wird aus dem folgenden Link- C-Programm entnommen , um die modulare multiplikative Inverse zweier relativ Primzahlen zu finden

Mohd Shibli
quelle
0

von der CPython Implementierung Quellcode :

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

Gemäß dem Kommentar über diesem Code kann er kleine negative Werte zurückgeben, sodass Sie möglicherweise prüfen können, ob er negativ ist, und n hinzufügen können, wenn er negativ ist, bevor Sie b zurückgeben.

Micsthepick
quelle
"So können Sie möglicherweise prüfen, ob negativ, und n hinzufügen, wenn negativ, bevor Sie b zurückgeben". Leider ist n zu diesem Zeitpunkt 0. (Sie müssten den ursprünglichen Wert von n speichern und verwenden.)
Don Hatch