Função inversa multiplicativa modular em Python


110

Algum módulo Python padrão contém uma função para calcular o inverso multiplicativo modular de um número, ou seja, um número y = invmod(x, p)assim x*y == 1 (mod p)? O Google não parece dar boas dicas sobre isso.

Claro, pode-se inventar um algoritmo euclidiano estendido de 10 linhas feito em casa , mas por que reinventar a roda?

Por exemplo, o método Java BigIntegertem modInverse. O Python não tem algo semelhante?


18
Em Python 3.8 (que deverá ser lançado no final deste ano), você vai ser capaz de usar o built-in powfunção para isso: y = pow(x, -1, p). Consulte bugs.python.org/issue36027 . Demorou apenas 8,5 anos desde a pergunta feita até que uma solução aparecesse na biblioteca padrão!
Mark Dickinson

4
Vejo @MarkDickinson modestamente negligenciado em mencionar que ey é o autor deste aprimoramento muito útil, então eu o farei. Obrigado por este trabalho, Mark, parece ótimo!
Don Hatch

Respostas:


128

Talvez alguém ache isso útil (dos 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
Eu estava tendo problemas com números negativos usando este algoritmo. modinv (-3, 11) não funcionou. Corrigi-lo substituindo egcd pela implementação na página dois deste pdf: anh.cs.luc.edu/331/notes/xgcd.pdf Espero que ajude!
Qaz

@Qaz Você também pode reduzir -3 módulo 11 para torná-lo positivo, neste caso modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Provavelmente é isso que o algoritmo do PDF faz em algum momento.
Thomas

1
Se acontecer de você estar usando sympy, então x, _, g = sympy.numbers.igcdex(a, m)faz o truque.
Lynn,

59

Se o seu módulo for primo (você o chama p), você pode simplesmente calcular:

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

Ou em Python adequado:

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

Aqui está alguém que implementou alguns recursos de teoria dos números em Python: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Aqui está um exemplo feito no prompt:

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

1
A exponenciação ingênua não é uma opção devido ao limite de tempo (e memória) para qualquer valor razoavelmente grande de p como, digamos, 1000000007.
dorserg

16
a exponenciação modular é feita com no máximo N * 2 multiplicações, onde N é o número de bits no expoente. usando um módulo de 2 ** 63-1, o inverso pode ser calculado no prompt e retorna um resultado imediatamente.
phkahler

3
Wow fantástico. Estou ciente da exponenciação rápida, mas não sabia que a função pow () pode receber o terceiro argumento, que a transforma em exponenciação modular.
dorserg

5
É por isso que você está usando Python certo? Porque é incrível :-)
phkahler

2
A propósito, isso funciona porque a partir do pequeno teorema de Fermat pow (x, m-1, m) deve ser 1. Portanto (pow (x, m-2, m) * x)% m == 1. Então pow (x, m-2, m) é o inverso de x (mod m).
Piotr Dabkowski

21

Você também pode querer dar uma olhada no módulo gmpy . É uma interface entre o Python e a biblioteca de precisão múltipla GMP. gmpy fornece uma função invert que faz exatamente o que você precisa:

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

Resposta atualizada

Conforme observado por @hyh, o gmpy.invert()retorna 0 se o inverso não existe. Isso corresponde ao comportamento da mpz_invert()função GMP . gmpy.divm(a, b, m)fornece uma solução geral para 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()retornará uma solução quando gcd(b,m) == 1e levanta uma exceção quando o inverso multiplicativo não existe.

Aviso: Sou o mantenedor atual da biblioteca gmpy.

Resposta atualizada 2

O gmpy2 agora gera uma exceção corretamente quando o inverso não existe:

>>> import gmpy2

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

Isso é legal até que encontrei em gmpy.invert(0,5) = mpz(0)vez de levantar um erro ...
h__

@hyh Você pode relatar isso como um problema na página inicial do gmpy? É sempre bom se problemas forem relatados.
Casevh de

BTW, existe uma multiplicação modular neste gmpypacote? (ou seja, alguma função que tem o mesmo valor, mas é mais rápida do que (a * b)% p?)
h__

Já foi proposto antes e estou experimentando métodos diferentes. A abordagem mais simples de apenas calcular (a * b) % pem uma função não é mais rápida do que apenas avaliar (a * b) % pem Python. A sobrecarga de uma chamada de função é maior do que o custo de avaliar a expressão. Consulte code.google.com/p/gmpy/issues/detail?id=61 para obter mais detalhes.
Casevh

2
A grande questão é que isso também funciona para módulos não primos.
sinédoque

13

A partir de 3.8 pythons, a função pow () pode receber um módulo e um número inteiro negativo. Veja aqui . O caso deles de como usá-lo é

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

8

Aqui está um one-liner para CodeFights ; é uma das soluções mais curtas:

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]

Ele retornará -1se Anão houver inverso multiplicativo em n.

Uso:

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

A solução usa o Algoritmo Euclidiano Estendido .


6

Sympy , um módulo python para matemática simbólica, tem uma função inversa modular integrada se você não quiser implementar a sua própria (ou se já estiver 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'

Isso não parece estar documentado no site da Sympy, mas aqui está a docstring: Sympy mod_inverse docstring no Github


2

Aqui está o meu código, pode ser desleixado, mas parece funcionar para mim de qualquer maneira.

# 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

O código acima não será executado em python3 e é menos eficiente em comparação com as variantes GCD. No entanto, esse código é muito transparente. Isso me levou a criar uma versão mais compacta:

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

1
Não há problema em explicar para as crianças e quando n == 7. Mas por outro lado, é equivalente a este "algoritmo":for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

Aqui está um 1-liner conciso que faz isso, sem usar nenhuma biblioteca externa.

# 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

Observe que isso é realmente apenas egcd, simplificado para retornar apenas o único coeficiente de interesse.


1

Para descobrir o inverso multiplicativo modular, eu recomendo usar o Algoritmo Euclidiano Estendido 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 haver um bug neste código: a = prevX - quotient * Xdeveria ser X = prevX - quotient * Xe deveria retornar prevX. FWIW, esta implementação é semelhante àquela no link de Qaz no comentário à resposta de Märt Bakhoff.
PM 2Ring de

1

Tento soluções diferentes deste tópico e no final uso 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 em Python


1
este código é inválido. returnno egcd está indendido de forma errada
ph4r05

0

Bem, eu não tenho uma função em python, mas tenho uma função em C que você pode facilmente converter para python, na função c abaixo, o algoritmo euclidiano estendido é usado para calcular o mod inverso.

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

Função 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

A referência à função C acima é obtida do seguinte programa C do link para encontrar o inverso multiplicativo modular de dois números primos relativamente


0

do código-fonte de implementação 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 acordo com o comentário acima deste código, ele pode retornar pequenos valores negativos, então você pode verificar se é negativo e adicionar n quando negativo antes de retornar b.


"para que você possa verificar se é negativo e adicionar n quando for negativo antes de retornar b". Infelizmente, n é 0 nesse ponto. (Você teria que salvar e usar o valor original de n.)
Don Hatch

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.