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?


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

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

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

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

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 .


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


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

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

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.


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

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


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


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.


"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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.