Модульна мультиплікативна зворотна функція в Python


110

Чи містить якийсь стандартний модуль Python функцію для обчислення модульного мультиплікативного зворотного числа, тобто таке число y = invmod(x, p), яке x*y == 1 (mod p)? Google, схоже, не дає жодних добрих підказів щодо цього.

Звичайно, можна придумати домашній 10-лайнер розширеного евклідового алгоритму , але навіщо винаходити колесо.

Наприклад, у Java BigIntegerє modInverseметод. Хіба у Python немає чогось подібного?


18
В Python 3.8 (повинен бути випущений пізніше в цьому році), ви будете мати можливість використовувати вбудовані powфункції для цього: y = pow(x, -1, p). Див. Bugs.python.org/issue36027 . Минуло лише 8,5 років від запитання до рішення, яке з'явилося в стандартній бібліотеці!
Марк Дікінсон

4
Я бачу, що @MarkDickinson скромно нехтував згадкою про те, що Ей є автором цього дуже корисного вдосконалення, тому я буду. Дякую за цю роботу, Марку, це чудово виглядає!
Дон Хетч

Відповіді:


128

Можливо, хтось знайде це корисним (із вікікниг ):

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
У мене були проблеми з від’ємними числами за допомогою цього алгоритму. modinv (-3, 11) не працював. Я виправив це, замінивши egcd на реалізацію на другій сторінці цього PDF: anh.cs.luc.edu/331/notes/xgcd.pdf Сподіваюся, що це допоможе!
Qaz

@Qaz Ви також можете просто зменшити -3 modulo 11, щоб зробити його позитивним, в цьому випадку modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Це, мабуть, те, що алгоритм у вашому PDF трапляється в якийсь момент.
Томас

1
Якщо ви випадково користуєтесь sympy, то x, _, g = sympy.numbers.igcdex(a, m)і хитрість.
Лінн

59

Якщо ваш модуль є простим (ви називаєте його p), ви можете просто обчислити:

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

Або в належному Python:

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

Ось хтось реалізував деякі можливості теорії чисел у Python: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Ось приклад, зроблений у підказці:

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

1
Наївна експоненція не є можливою через обмеження часу (і пам'яті) для будь-якого досить великого значення p, як скажімо, 1000000007.
dorserg

16
модульна експоненція робиться з не більше N * 2 множеннями, де N - кількість бітів в експоненті. використовуючи модуль 2 ** 63-1, обернена може бути обчислена в підказці і негайно повертає результат.
phkahler

3
Ого, чудовий. Я знаю про швидку експоненцію, я просто не знав, що функція pow () може приймати третій аргумент, що перетворює його на модульну експоненцію.
dorserg

5
Ось чому ви правильно використовуєте Python? Тому що це приголомшливо :-)
phkahler

2
До речі, це працює, тому що з малої теореми Ферма pow (x, m-1, m) повинно бути 1. Отже (pow (x, m-2, m) * x)% m == 1. Отже, pow (x, m-2, m) - обернена x (mod m).
Пьотр Дабковський

21

Ви також можете подивитися модуль gmpy . Це інтерфейс між Python та багатоточною бібліотекою GMP. gmpy забезпечує функцію інвертування, яка виконує саме те, що вам потрібно:

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

Оновлена ​​відповідь

Як зазначає @hyh, gmpy.invert()повертає 0, якщо обернено не існує. Це відповідає поведінці функції GMP mpz_invert(). gmpy.divm(a, b, m)забезпечує загальне рішення для 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()поверне рішення, коли gcd(b,m) == 1і зробить виняток, коли мультиплікативної інверсії не існує.

Відмова: Я є поточним обслуговувачем бібліотеки gmpy.

Оновлена ​​відповідь 2

gmpy2 тепер належним чином створює виняток, коли обернено не існує:

>>> import gmpy2

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

Це круто, поки я не знайшов gmpy.invert(0,5) = mpz(0)замість того, щоб викликати помилку ...
h__

@hyh Чи можете ви повідомити про це як проблему на домашній сторінці gmpy? Завжди цінується, якщо повідомляються про проблеми.
casevh

BTW, чи є модульне множення в цьому gmpyпакеті? (тобто деяка функція, яка має те саме значення, але швидша, ніж (a * b)% p?)
h__

Це було запропоновано раніше, і я експериментую з різними методами. Найпростіший підхід просто обчислити (a * b) % pфункцію не швидше, ніж просто оцінити (a * b) % pв Python. Накладні витрати для виклику функції більше, ніж вартість оцінки виразу. Докладнішу інформацію див. У коді .google.com/p/gmpy/isissue/detail?id = 61 .
casevh

2
Чудова річ, що це також працює для непрості модулів.
синекдоха

13

Починаючи з 3,8 функція pow () пітона може приймати модуль і від'ємне ціле число. Дивіться тут . Їх справа в тому, як ним користуватися

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

8

Ось однолінійка для CodeFights ; це одне з найкоротших рішень:

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]

Він повернеться, -1якщо в Aньому немає мультиплікативного зворотного n.

Використання:

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

У рішенні використовується розширений евклідовий алгоритм .


6

Sympy , модуль пітона для символічної математики, має вбудовану модульну зворотну функцію, якщо ви не хочете реалізувати свою власну (або якщо ви вже використовуєте 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'

Це не здається документально зафіксовано на веб-сайті Sympy, але ось docstring : Sympy mod_inverse docstring в Github


2

Ось мій код, він може бути неохайним, але він, здається, працює для мене все одно.

# 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

Наведений вище код не працюватиме в python3 та є менш ефективним порівняно з варіантами GCD. Однак цей код дуже прозорий. Це спричинило мене до створення більш компактної версії:

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

1
Це добре, щоб пояснити це дітям, і коли n == 7. Але в іншому випадку мова йде про еквівалент цього «алгоритму»:for i in range(2, n): if i * a % n == 1: return i
Томаш

2

Ось стислий 1-лайнер, який робить це, не використовуючи жодних зовнішніх бібліотек.

# 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

Зауважте, що це дійсно просто egcd, упорядкований, щоб повернути лише єдиний коефіцієнт інтересу.


1

Для з'ясування модульного мультиплікативного зворотного я рекомендую використовувати розширений евклідовий алгоритм так:

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

У цьому коді виявляється помилка: a = prevX - quotient * Xповинна бути X = prevX - quotient * X, і вона повинна повернутися prevX. FWIW, ця реалізація схожа на посилання Каза у коментарі до відповіді Мерта Бакхофа.
PM 2Ring

1

Я пробую різні рішення з цієї нитки, і врешті-решт використовую це:

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


1
цей код недійсний. returnу egcd позначається неправильно
ph4r05

0

Ну, у мене немає функції в python, але у мене є функція в C, яку ви можете легко перетворити на python, в наведеній нижче c функції розширений евклідовий алгоритм використовується для обчислення зворотного мода.

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

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

Посилання на вищевказану функцію C взято з наступної посилання програми C для пошуку модульної мультиплікативної інверсії двох відносно простих чисел


0

з вихідного коду реалізації 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")

відповідно до коментаря вище цього коду, він може повертати невеликі негативні значення, тому ви потенційно можете перевірити, чи є негативом, і додати n, коли негативне, перш ніж повернути b.


"щоб ви могли потенційно перевірити, чи негативний, і додати n, коли негативний, перш ніж повертати b". На жаль, n в цій точці дорівнює 0. (Вам потрібно було б зберегти та використовувати початкове значення п.)
Дон Хетч

-2

Багато з наведених вище посилань перервано на 23.01.2017. Я знайшов таке втілення: https://courses.csail.mit.edu/6.857/2016/files/ffield.py


Уникайте лише відповідей на посилання. Як ви заявили у своєму електронному листі, посилання можуть розірватися.
Джефф

Емін Мартиніан, автор цього модуля, упакував його як pypi.python.org/pypi/pyfinite/1.5
Ден Д.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.