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

112

Содержит ли какой-либо стандартный модуль 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 скромно забыл упомянуть, что ey является автором этого очень полезного улучшения, так что я это сделаю. Спасибо за эту работу, Марк, выглядит отлично!
Дон Хэтч

Ответы:

129

Может быть, кому-то это пригодится (из викиучебников ):

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 по модулю 11, чтобы сделать его положительным, в данном случае modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Вероятно, именно это и делает алгоритм в вашем PDF-файле в какой-то момент.
Thomas
1
Если вы случайно используете sympy, то x, _, g = sympy.numbers.igcdex(a, m)поможет.
Lynn
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
Phkahler
источник
1
Наивное возведение в степень - не вариант из-за ограничения по времени (и памяти) для любого достаточно большого значения p, например, 1000000007.
dorserg
16
модульное возведение в степень выполняется не более чем с N * 2 умножениями, где N - количество бит в экспоненте. используя модуль 2 ** 63-1, обратное значение может быть вычислено в командной строке и немедленно вернет результат.
phkahler
3
Вау, круто. Я знаю о быстром возведении в степень, я просто не знал, что функция pow () может принимать третий аргумент, который превращает ее в модульное возведение в степень.
dorserg
5
Вот почему вы правильно используете Python? Потому что это
круто
2
Кстати, это работает, потому что из маленькой теоремы Ферма pow (x, m-1, m) должно быть 1. Следовательно (pow (x, m-2, m) * x)% m == 1. Итак, pow (x, m-2, m) является обратным x (mod m).
Piotr Dabkowski 04
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
casevh
источник
Это круто, пока я не нашел gmpy.invert(0,5) = mpz(0)вместо того, чтобы
выдавать
@hyh Можете ли вы сообщить об этом как о проблеме на домашней странице gmpy? Мы всегда приветствуем сообщения о проблемах.
casevh
Кстати, есть ли в этом gmpyпакете модульное умножение ? (т.е. некоторая функция, которая имеет то же значение, но работает быстрее, чем (a * b)% p?)
h__
Это было предложено ранее, и я экспериментирую с другими методами. Самый простой подход - просто вычисление (a * b) % pв функции - не быстрее, чем просто вычисление (a * b) % pв Python. Накладные расходы на вызов функции больше, чем затраты на оценку выражения. См. Code.google.com/p/gmpy/issues/detail?id=61 для получения дополнительных сведений.
casevh
2
Замечательно то, что это работает и для непростых модулей.
synecdoche
14

Начиная с версии 3.8 pythons, функция pow () может принимать модуль и отрицательное целое число. Смотрите здесь . Их аргументы в пользу того, как его использовать,

>>> pow(38, -1, 97)
23
>>> 23 * 38 % 97 == 1
True
A_Arnold
источник
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

Решение использует расширенный алгоритм Евклида .

HKTonyLee
источник
6

Sympy , модуль Python для символьной математики, имеет встроенную модульную обратную функцию, если вы не хотите реализовывать свою собственную (или если вы уже используете 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, но вот строка документации: 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
BvdM
источник
1
Это нормально объяснять детям и когда n == 7. Но в остальном речь идет об эквиваленте этого «алгоритма»:for i in range(2, n): if i * a % n == 1: return i
Томаш Гандор
2

Вот краткий однострочный файл, который делает это без использования каких-либо внешних библиотек.

# 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, эта реализация аналогична той, что указана в ссылке Qaz в комментарии к ответу Мярта Бахоффа.
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 неправильный
отступ
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.

Micsthepick
источник
"так что вы потенциально можете проверить, является ли отрицательным, и добавить n, если отрицательно, прежде чем вернуть b". К сожалению, в этот момент n равно 0. (Вам нужно будет сохранить и использовать исходное значение n.)
Дон Хэтч,
-2

По состоянию на 23.01.2017 многие из приведенных выше ссылок не работают. Я нашел эту реализацию: https://courses.csail.mit.edu/6.857/2016/files/ffield.py

OutputLogic
источник
Избегайте ответов только по ссылкам. Как вы указали в своем электронном письме, ссылки могут не работать.
Джефф
Эмин Мартиниан, автор этого модуля, упаковал его как pypi.python.org/pypi/pyfinite/1.5
Дэн Д.