Función inversa multiplicativa modular en Python

110

¿Algún módulo estándar de Python contiene una función para calcular el inverso multiplicativo modular de un número, es decir, un número y = invmod(x, p)tal que x*y == 1 (mod p)? Google no parece dar buenas pistas sobre esto.

Por supuesto, uno puede idear 10 líneas caseras de algoritmo euclidiano extendido , pero ¿por qué reinventar la rueda?

Por ejemplo, el método BigIntegerhas de Java modInverse. ¿No tiene Python algo similar?

dorserg
fuente
18
En Python 3.8 (que se publicará a finales de este año), usted será capaz de utilizar el incorporado en powfunción de esto: y = pow(x, -1, p). Consulte bugs.python.org/issue36027 . ¡Solo pasaron 8.5 años desde que se hizo la pregunta hasta que apareció una solución en la biblioteca estándar!
Mark Dickinson
4
Veo que @MarkDickinson se olvidó modestamente de mencionar que ey es el autor de esta mejora muy útil, así que lo haré. Gracias por este trabajo, Mark, ¡se ve genial!
Don Hatch

Respuestas:

128

Quizás alguien encuentre esto útil (de wikilibros ):

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
fuente
1
Tenía problemas con los números negativos al usar este algoritmo. modinv (-3, 11) no funcionó. Lo arreglé reemplazando egcd con la implementación en la página dos de este pdf: anh.cs.luc.edu/331/notes/xgcd.pdf ¡ Espero que ayude!
Qaz
@Qaz También puede reducir -3 módulo 11 para hacerlo positivo, en este caso modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Eso es probablemente lo que hace el algoritmo en su PDF en algún momento.
Thomas
1
Si está usando sympy, entonces x, _, g = sympy.numbers.igcdex(a, m)funciona.
Lynn
59

Si su módulo es primo (lo llama usted p), simplemente puede calcular:

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

O en Python propiamente dicho:

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

Aquí hay alguien que ha implementado algunas capacidades de teoría de números en Python: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Aquí hay un ejemplo hecho en el indicador:

m = 1000000007
x = 1234567
y = pow(x,m-2,m)
y
989145189L
x*y
1221166008548163L
x*y % m
1L
phkahler
fuente
1
La potenciación ingenua no es una opción debido al límite de tiempo (y memoria) para cualquier valor razonablemente grande de p como, por ejemplo, 1000000007.
dorserg
16
la exponenciación modular se realiza con un máximo de N * 2 multiplicaciones, donde N es el número de bits en el exponente. utilizando un módulo de 2 ** 63-1, la inversa se puede calcular en el indicador y devuelve un resultado inmediatamente.
phkahler
3
Wow increible. Soy consciente de la exponenciación rápida, simplemente no sabía que la función pow () puede tomar el tercer argumento, lo que la convierte en exponenciación modular.
dorserg
5
Por eso estás usando Python, ¿verdad? Porque es increíble :-)
phkahler
2
Por cierto, esto funciona porque de Fermat el pequeño teorema pow (x, m-1, m) debe ser 1. Por lo tanto (pow (x, m-2, m) * x)% m == 1. Entonces pow (x, m-2, m) es la inversa de x (mod m).
Piotr Dabkowski
21

Es posible que también desee ver el módulo gmpy . Es una interfaz entre Python y la biblioteca de precisión múltiple GMP. gmpy proporciona una función de inversión que hace exactamente lo que necesita:

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

Respuesta actualizada

Como señaló @hyh, gmpy.invert()devuelve 0 si no existe el inverso. Eso coincide con el comportamiento de la mpz_invert()función de GMP . gmpy.divm(a, b, m)proporciona una solución general 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()devolverá una solución cuando gcd(b,m) == 1y genere una excepción cuando el inverso multiplicativo no exista.

Descargo de responsabilidad: soy el mantenedor actual de la biblioteca gmpy.

Respuesta actualizada 2

gmpy2 ahora genera correctamente una excepción cuando la inversa no existe:

>>> import gmpy2

>>> gmpy2.invert(0,5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: invert() no inverse exists
casevh
fuente
Esto es genial hasta que encontré en gmpy.invert(0,5) = mpz(0)lugar de generar un error ...
h__
@hyh ¿Puede informar esto como un problema en la página de inicio de gmpy? Siempre se agradece si se informan problemas.
casevh
Por cierto, ¿hay una multiplicación modular en este gmpypaquete? (es decir, alguna función que tiene el mismo valor pero es más rápida que (a * b)% p?)
h__
Se ha propuesto antes y estoy experimentando con diferentes métodos. El enfoque más simple de simplemente calcular (a * b) % pen una función no es más rápido que evaluar (a * b) % pen Python. La sobrecarga de una llamada a función es mayor que el costo de evaluar la expresión. Consulte code.google.com/p/gmpy/issues/detail?id=61 para obtener más detalles.
casevh
2
Lo bueno es que esto también funciona para módulos no principales.
sinécdoque
13

A partir de 3.8 pitones, la función pow () puede tomar un módulo y un entero negativo. Vea aquí . Su caso de cómo usarlo es

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

Aquí hay una sola línea para CodeFights ; es una de las soluciones más cortas:

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]

Volverá -1si Ano tiene inverso multiplicativo n.

Uso:

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

La solución utiliza el algoritmo euclidiano extendido .

HKTonyLee
fuente
6

Sympy , un módulo de Python para matemáticas simbólicas, tiene una función inversa modular incorporada si no desea implementar la suya propia (o si ya está 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'

Esto no parece estar documentado en el sitio web de Sympy, pero aquí está la cadena de documentos: Sympy mod_inverse docstring en Github

Chris Chudzicki
fuente
2

Aquí está mi código, puede que sea descuidado, pero parece funcionar para mí de todos modos.

# 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
fuente
2

El código anterior no se ejecutará en python3 y es menos eficiente en comparación con las variantes de GCD. Sin embargo, este código es muy transparente. Me impulsó a crear una versión más compacta:

def imod(a, n):
 c = 1
 while (c % a > 0):
     c += n
 return c // a
BvdM
fuente
1
Está bien explicárselo a los niños y cuándo n == 7. Pero de lo contrario, se trata del equivalente de este "algoritmo":for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor
2

Aquí hay una línea simple concisa que lo hace, sin usar bibliotecas externas.

# 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

Tenga en cuenta que esto es realmente solo egcd, optimizado para devolver solo el coeficiente de interés único.

Don Hatch
fuente
1

Para descubrir el inverso multiplicativo modular, recomiendo usar el algoritmo euclidiano extendido como este:

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
fuente
Parece haber un error en este código: a = prevX - quotient * Xdebería X = prevX - quotient * Xhaberlo y debería volver prevX. FWIW, esta implementación es similar a la del enlace de Qaz en el comentario a la respuesta de Märt Bakhoff.
PM 2 Ring
1

Intento diferentes soluciones de este hilo y al final utilizo esta:

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

Al Po
fuente
1
este código no es válido. returnen egcd se incluye de manera incorrecta
ph4r05
0

Bueno, no tengo una función en Python, pero tengo una función en C que puedes convertir fácilmente a Python, en la siguiente función c, el algoritmo euclidiano extendido se usa para calcular la modificación 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;}

Función 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

La referencia a la función C anterior se toma del siguiente enlace del programa C para encontrar el inverso multiplicativo modular de dos números relativamente primos

Mohd Shibli
fuente
0

del código fuente de implementación de 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")

de acuerdo con el comentario sobre este código, puede devolver pequeños valores negativos, por lo que podría verificar si es negativo y agregar n cuando sea negativo antes de devolver b.

micsthepick
fuente
"por lo que potencialmente podría verificar si es negativo y agregar n cuando sea negativo antes de devolver b". Desafortunadamente, n es 0 en ese punto. (Tendría que guardar y usar el valor original de n.)
Don Hatch