Funzione inversa moltiplicativa modulare in Python


110

Qualche modulo Python standard contiene una funzione per calcolare l'inverso moltiplicativo modulare di un numero, cioè un numero y = invmod(x, p)tale che x*y == 1 (mod p)? Google non sembra dare buoni suggerimenti su questo.

Certo, si possono inventare 10 linee fatte in casa di algoritmo euclideo esteso , ma perché reinventare la ruota.

Ad esempio, il metodo BigIntegerhas di Java modInverse. Python non ha qualcosa di simile?


18
In Python 3.8 (dovrebbe essere rilasciato entro la fine dell'anno), sarete in grado di utilizzare il built-in powfunzione per questo: y = pow(x, -1, p). Vedi bugs.python.org/issue36027 . Sono passati solo 8,5 anni dalla domanda posta a una soluzione che appariva nella libreria standard!
Mark Dickinson

4
Vedo @MarkDickinson modestamente trascurato di menzionare che ey è l'autore di questo miglioramento molto utile, quindi lo farò. Grazie per questo lavoro, Mark, sembra fantastico!
Don Hatch

Risposte:


128

Forse qualcuno lo troverà utile (da 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
Ho avuto problemi con i numeri negativi utilizzando questo algoritmo. modinv (-3, 11) non ha funzionato. L'ho risolto sostituendo egcd con l'implementazione a pagina due di questo pdf: anh.cs.luc.edu/331/notes/xgcd.pdf Spero che aiuti!
Qaz

@Qaz Puoi anche ridurre -3 modulo 11 per renderlo positivo, in questo caso modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Questo è probabilmente ciò che l'algoritmo nel tuo PDF fa a un certo punto.
Thomas il

1
Se ti capita di usare sympy, allora x, _, g = sympy.numbers.igcdex(a, m)fa il trucco.
Lynn

59

Se il tuo modulo è primo (lo chiami p), puoi semplicemente calcolare:

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

O in Python vero e proprio:

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

Ecco qualcuno che ha implementato alcune funzionalità di teoria dei numeri in Python: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Ecco un esempio eseguito al prompt:

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

1
L'esponenziazione ingenua non è un'opzione a causa del limite di tempo (e memoria) per qualsiasi valore ragionevolmente grande di p come 1000000007.
dorserg

16
l'esponenziazione modulare viene eseguita con al massimo N * 2 moltiplicazioni dove N è il numero di bit nell'esponente. utilizzando un modulo di 2 ** 63-1 l'inverso può essere calcolato al prompt e restituisce immediatamente un risultato.
phkahler

3
Wow fantastico. Sono consapevole del rapido esponenziazione, semplicemente non ero consapevole che la funzione pow () può prendere il terzo argomento che lo trasforma in esponenziazione modulare.
Dorserg

5
Ecco perché stai usando Python, giusto? Perché è fantastico :-)
phkahler

2
Tra l'altro funziona perché dal piccolo teorema di Fermat pow (x, m-1, m) deve essere 1. Quindi (pow (x, m-2, m) * x)% m == 1. Quindi pow (x, m-2, m) è l'inverso di x (mod m).
Piotr Dabkowski

21

Potresti anche voler guardare il modulo gmpy . È un'interfaccia tra Python e la libreria GMP a precisione multipla. gmpy fornisce una funzione di inversione che fa esattamente ciò di cui hai bisogno:

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

Risposta aggiornata

Come notato da @hyh, gmpy.invert()restituisce 0 se l'inverso non esiste. Ciò corrisponde al comportamento della mpz_invert()funzione GMP . gmpy.divm(a, b, m)fornisce una soluzione generale a 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()restituirà una soluzione quando gcd(b,m) == 1e solleverà un'eccezione quando l'inverso moltiplicativo non esiste.

Disclaimer: sono l'attuale manutentore della libreria gmpy.

Risposta aggiornata 2

gmpy2 ora solleva correttamente un'eccezione quando l'inverso non esiste:

>>> import gmpy2

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

Questo è bello finché non ho trovato gmpy.invert(0,5) = mpz(0)invece di generare un errore ...
h__

@hyh Puoi segnalarlo come un problema nella home page di gmpy? È sempre apprezzato se vengono segnalati problemi.
casevh

A proposito, c'è una moltiplicazione modulare in questo gmpypacchetto? (cioè una funzione che ha lo stesso valore ma è più veloce di (a * b)% p?)
h__

È stato proposto prima e sto sperimentando diversi metodi. L'approccio più semplice del solo calcolo (a * b) % pin una funzione non è più veloce della semplice valutazione (a * b) % pin Python. L'overhead per una chiamata di funzione è maggiore del costo di valutazione dell'espressione. Vedi code.google.com/p/gmpy/issues/detail?id=61 per maggiori dettagli.
casevh

2
La cosa bella è che funziona anche per moduli non primi.
sineddoche

13

A partire da 3,8 pitoni la funzione pow () può assumere un modulo e un numero intero negativo. Vedi qui . Il loro caso su come usarlo è

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

8

Ecco un one-liner per CodeFights ; è una delle soluzioni più brevi:

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]

Tornerà -1se Anon ha un inverso moltiplicativo in n.

Uso:

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

La soluzione utilizza l' algoritmo euclideo esteso .


6

Sympy , un modulo python per la matematica simbolica, ha una funzione inversa modulare incorporata se non vuoi implementare la tua (o se stai già usando 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'

Questo non sembra essere documentato sul sito web di Sympy, ma ecco la docstring: Sympy mod_inverse docstring su Github


2

Ecco il mio codice, potrebbe essere sciatto ma sembra funzionare comunque per me.

# 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

Il codice sopra non verrà eseguito in python3 ed è meno efficiente rispetto alle varianti GCD. Tuttavia, questo codice è molto trasparente. Mi ha spinto a creare una versione più compatta:

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

1
Va bene spiegarlo ai bambini e quando n == 7. Ma per il resto si tratta di un equivalente di questo "algoritmo":for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

Ecco un conciso 1-liner che lo fa, senza utilizzare alcuna libreria esterna.

# 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

Nota che questo è in realtà solo egcd, semplificato per restituire solo il singolo coefficiente di interesse.


1

Per capire l'inverso moltiplicativo modulare, consiglio di utilizzare l'algoritmo euclideo esteso in questo modo:

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

Sembra che ci sia un bug in questo codice: a = prevX - quotient * Xdovrebbe essere X = prevX - quotient * Xe dovrebbe tornare prevX. FWIW, questa implementazione è simile a quella nel collegamento di Qaz nel commento alla risposta di Märt Bakhoff.
PM 2 Ring,

1

Provo diverse soluzioni da questo thread e alla fine utilizzo questo:

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


1
questo codice non è valido. returnin egcd è inserito in modo sbagliato
ph4r05

0

Bene, non ho una funzione in python ma ho una funzione in C che puoi facilmente convertire in python, nella funzione c sotto viene utilizzato l'algoritmo euclideo esteso per calcolare la mod inversa.

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

Funzione 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

Il riferimento alla funzione C sopra è preso dal seguente collegamento programma C per trovare l'inverso moltiplicativo modulare di due numeri primi relativi


0

dal codice sorgente dell'implementazione 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")

secondo il commento sopra questo codice, può restituire piccoli valori negativi, quindi potresti potenzialmente controllare se negativo e aggiungere n quando negativo prima di restituire b.


"quindi potresti potenzialmente controllare se negativo e aggiungere n quando negativo prima di restituire b". Sfortunatamente n è 0 a quel punto. (Dovresti salvare e utilizzare il valore originale di n.)
Don Hatch

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.