ฟังก์ชันผกผันการคูณแบบโมดูลาร์ใน Python


110

โมดูล Python มาตรฐานบางตัวมีฟังก์ชันในการคำนวณค่าผกผันการคูณแบบโมดูลาร์หรือไม่y = invmod(x, p)เช่นจำนวนนั้นx*y == 1 (mod p)หรือไม่ Google ดูเหมือนจะไม่ได้ให้คำแนะนำที่ดีเกี่ยวกับเรื่องนี้

แน่นอนว่าเราสามารถสร้างอัลกอริธึมแบบยุคลิดแบบขยายที่ผลิตเองในบ้านได้ 10 สายแต่ทำไมต้องสร้างวงล้อขึ้นมาใหม่

ตัวอย่างเช่น Java BigIntegerมีmodInverseวิธีการ Python ไม่มีอะไรที่คล้ายกัน?


18
ในหลาม 3.8 (เนื่องจากได้รับการปล่อยตัวในช่วงปลายปีนี้) คุณจะสามารถที่จะใช้ในตัวฟังก์ชั่นสำหรับการนี้:pow y = pow(x, -1, p)ดูbugs.python.org/issue36027 ใช้เวลาเพียง 8.5 ปีจากคำถามที่ถูกถามไปยังโซลูชันที่ปรากฏในห้องสมุดมาตรฐาน!
Mark Dickinson

4
ฉันเห็น @MarkDickinson ละเลยอย่างสุภาพที่จะพูดถึงว่า ey เป็นผู้เขียนการปรับปรุงที่มีประโยชน์มากนี้ดังนั้นฉันจะทำ ขอบคุณสำหรับงานนี้ Mark มันดูดีมาก!
Don Hatch

คำตอบ:


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 โมดูโล 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

1
การยกกำลังแบบไร้เดียงสาไม่ใช่ทางเลือกเนื่องจากเวลา (และหน่วยความจำ) จำกัด สำหรับค่า p ที่ใหญ่พอสมควรเช่นพูด 1000000007
dorserg

16
การยกกำลังแบบโมดูลาร์ทำได้โดยการคูณไม่เกิน N * 2 โดยที่ N คือจำนวนบิตในเลขชี้กำลัง โดยใช้โมดูลัส 2 ** 63-1 สามารถคำนวณผกผันได้ที่พรอมต์และส่งกลับผลลัพธ์ทันที
phkahler

3
ว้าวสุดยอดมาก ฉันตระหนักถึงการยกกำลังด่วนฉันไม่ทราบว่าฟังก์ชัน pow () สามารถรับอาร์กิวเมนต์ที่สามซึ่งจะเปลี่ยนเป็นเลขชี้กำลังแบบโมดูลาร์
dorserg

5
นั่นเป็นเหตุผลที่คุณใช้ Python ใช่ไหม เพราะมันยอดเยี่ยมมาก :-)
phkahler

2
โดยวิธีนี้ได้ผลเนื่องจากจาก Fermat ทฤษฎีบทเล็ก ๆ น้อย ๆ (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 มีฟังก์ชัน invert ที่ตอบสนองสิ่งที่คุณต้องการ:

>>> 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 ค่าโสหุ้ยสำหรับการเรียกใช้ฟังก์ชันมากกว่าค่าใช้จ่ายในการประเมินนิพจน์ ดูcode.google.com/p/gmpy/issues/detail?id=61สำหรับรายละเอียดเพิ่มเติม
casevh

2
สิ่งที่ยอดเยี่ยมคือมันยังใช้ได้กับโมดูลิที่ไม่ใช่ไพรม์
synecdoche

13

จาก 3.8 pythons 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ถ้าไม่มีผกผันในAn

การใช้งาน:

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

วิธีการแก้ปัญหาใช้ขยายยุคลิดอัลกอริทึม


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 แต่นี่คือ 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
Tomasz 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 prevXFWIW การใช้งานนี้คล้ายกับในลิงก์ของ Qazในความคิดเห็นไปยังคำตอบของMärt Bakhoff
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 เพื่อค้นหา Modular Multiplicative Inverse ของเลขหลักสองตัว


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 ณ จุดนั้น (คุณต้องบันทึกและใช้ค่าดั้งเดิมของ n)
Don Hatch

-2

ลิงก์หลายรายการด้านบนเสียในวันที่ 23 มกราคม 2560 ฉันพบการใช้งานนี้: https://courses.csail.mit.edu/6.857/2016/files/ffield.py


หลีกเลี่ยงคำตอบเฉพาะลิงก์ ตามที่คุณระบุไว้ในอีเมลลิงก์อาจพังได้
Jeff

Emin Martinian ผู้เขียนโมดูลดังกล่าวได้บรรจุเป็นpypi.python.org/pypi/pyfinite/1.5
Dan D.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.