Pythonのモジュラー乗法逆関数


110

一部の標準Pythonモジュールには、数値のモジュラー乗法逆数を計算する関数が含まれてy = invmod(x, p)x*y == 1 (mod p)ますか?グーグルはこれについて良いヒントを与えていないようです。

もちろん、拡張ユークリッドアルゴリズムの自家製の10ライナーを思い付くことができますが、なぜ車輪を再発明するのでしょうか。

たとえば、JavaにBigIntegerはhas modInverseメソッドがあります。Pythonには似たようなものはありませんか?


18
Python 3.8(今年後半にリリース予定)では、このための組み込みpow関数を使用できますy = pow(x, -1, p)bugs.python.org/issue36027を参照してください。質問されてから標準ライブラリに表示されるソリューションまで8.5年しかかかりませんでした。
マークディキンソン

4
@MarkDickinsonは、eyがこの非常に便利な拡張機能の作者であることをさほど気にせずに無視しているので、私はそうします。マークさん、この作品をありがとうございます。
ドンハッチ

回答:


128

多分誰かが(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
このアルゴリズムを使用して負の数の問題がありました。modinv(-3、11)は機能しませんでした。egcdをこのpdfの2ページ目の実装に置き換えて修正しました:anh.cs.luc.edu/331/notes/xgcd.pdfよろしくお願いします!
Qaz

@Qaz 11を法として-3を単純に減らして正にすることもできます。この場合、modinv(-3、11)== modinv(-3 + 11、11)== modinv(8、11)です。これはおそらく、PDFのアルゴリズムがたまたま行うことです。
トーマス

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

1
ナイーブ累乗は言う1000000007.等Pの任意の合理的に大きな値のため、時間(とメモリ)のオプションの限界ではありません
dorserg

16
モジュラー指数は、最大N * 2の乗算で行われます。Nは指数のビット数です。2 ** 63-1の係数を使用すると、逆数をプロンプトで計算でき、結果をすぐに返します。
phkahler、2011年

3
うわ〜すごい。私は指数関数の素早いことを知っていましたが、pow()関数が3番目の引数を取り、それをモジュラー指数に変換できることを知りませんでした。
dorserg

5
それがPythonを正しく使用している理由ですか?素晴らしいからです:-)
phkahler '25年

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

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 2013

ところで、このgmpyパッケージにはモジュラー乗算がありますか?(つまり、同じ値を持っているが(a * b)% p?よりも速い関数)
h__

これは以前に提案されたものであり、私はさまざまな方法で実験しています。(a * b) % p関数で計算するだけの最も単純なアプローチは(a * b) % p、Pythonで評価するよりも速くはありません。関数呼び出しのオーバーヘッドは、式を評価するコストよりも大きくなります。詳細については、code.google.com / p / gmpy / issues / detail?id = 61をご覧ください。
casevh 2013

2
素晴らしいことは、これが非素数の係数に対しても機能することです。
synecdoche 2013年

13

3.8以降、pythonのpow()関数は係数と負の整数を取ることができます。こちらをご覧ください。それを使用する方法についての彼らのケースは

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

8

これがCodeFightsのワンライナーです。これは最短のソリューションの1つです。

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

シンボリック数学用のpythonモジュールである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のWebサイトに記載されていないようですが、docstringは次のとおりですGithubのSympy mod_inverse docstring


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
トマシュGandor

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、この実装はマーツ・バホフの回答へのコメントのQaz のリンクの実装と似ています。
PM 2Ring 2015年

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

PythonのModular_inverse


1
このコードは無効です。returnegcdは間違った方法で使用されています
ph4r05

0

まあ、私はpythonには関数はありませんが、Cには簡単にpythonに変換できる関数があります。以下のcでは、拡張ユークリッドアルゴリズムを使用して逆modを計算しています。

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プログラムから引用され、2つの比較的素数のモジュラー乗法逆数を見つけます。


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を返すことができます。


「したがって、bを返す前に、負かどうかをチェックし、負の場合はnを追加することができます。」残念ながら、その時点ではnは0です。(あなたは、n個の元の値を保存する必要があり、使用すると思います。)
ドン・ハッチ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.