[Python] 模 n 乘法的逆元计算器

背景

Python 扩展欧几里得算法 一文中,我们已经接触了扩展欧几里得算法。借助扩展欧几里得算法,我们可以高效地计算模 nn n 乘法的逆元。本文会介绍相关的内容。

正文

对正整数 aa a 和 nn n 而言,是否可以找到一个整数 xx x 使得 ax≡1(modn) a x\equiv 1 \pmod{n} ax≡1(modn) 成立呢?

由于以下两个等式成立(其中 kk k 是任意整数),我们只需要关心满足 0≤a,x<n0\le a,x \lt n 0≤a,x<n 条件的整数 a,xa,x a,x 就够了。

  • ax≡(a−kn)x(modn)ax\equiv (a-kn)x \pmod{n} ax≡(a−kn)x(modn)
  • ax≡a(x−kn)(modn)ax\equiv a(x-kn) \pmod{n} ax≡a(x−kn)(modn)

我们可以先用比较小的 nn n 试一试。

用较小的 nn n 做尝试

n=1n=1 n=1 时

aa a 满足 ax≡1(mod1) ax\equiv 1 \pmod{1} ax≡1(mod1) 和 0≤x<10\le x\lt 1 0≤x<1 的 xx x
00 0 00 0

n=2n=2 n=2 时

aa a 满足 ax≡1(mod2) ax\equiv 1 \pmod{2} ax≡1(mod2) 和 0≤x<20\le x\lt 2 0≤x<2 的 xx x
00 0 (不存在)
11 1 11 1

n=3n=3 n=3 时

aa a 满足 ax≡1(mod3) ax\equiv 1 \pmod{3} ax≡1(mod3) 和 0≤x<30\le x\lt 3 0≤x<3 的 xx x
00 0 (不存在)
11 1 11 1
22 2 22 2

n=4n=4 n=4 时

aa a 满足 ax≡1(mod4) ax\equiv 1 \pmod{4} ax≡1(mod4) 和 0≤x<40\le x\lt 4 0≤x<4 的 xx x
00 0 (不存在)
11 1 11 1
22 2 (不存在)
33 3 33 3

n=5n=5 n=5 时

aa a 满足 ax≡1(mod5) ax\equiv 1 \pmod{5} ax≡1(mod5) 和 0≤x<50\le x\lt 5 0≤x<5 的 xx x
00 0 (不存在)
11 1 11 1
22 2 33 3
33 3 22 2
44 4 44 4

n=6n=6 n=6 时

aa a 满足 ax≡1(mod6) ax\equiv 1 \pmod{6} ax≡1(mod6) 和 0≤x<60\le x\lt 6 0≤x<6 的 xx x
00 0 (不存在)
11 1 11 1
22 2 (不存在)
33 3 (不存在)
44 4 (不存在)
55 5 55 5

n=7n=7 n=7 时

aa a 满足 ax≡1(mod7) ax\equiv 1 \pmod{7} ax≡1(mod7) 和 0≤x<70\le x\lt 7 0≤x<7 的 xx x
00 0 (不存在)
11 1 11 1
22 2 44 4
33 3 55 5
44 4 22 2
55 5 33 3
66 6 66 6

一些猜测

从上一小节的数据来看,似乎有以下规律成立(有待证明或者证伪)

  1. aa a 和 nn n 互质(即 gcd(a,n)=1gcd(a,n)=1 gcd(a,n)=1)时,似乎总能找到满足 ax≡1(modn) a x \equiv 1 \pmod{n} ax≡1(modn) 的 xx x
  2. 当我们限制 xx x 满足 0≤x<n0\le x\lt n 0≤x<n 时,能让 ax≡1(modn) a x \equiv 1 \pmod{n} ax≡1(modn) 成立的 xx x 似乎最多只有 11 1 个。

第一个规律,我们可以用扩展欧几里得算法来证明。当 aa a 和 nn n 互质(即 gcd(a,n)=1gcd(a,n)=1 gcd(a,n)=1)时,借助扩展欧几里得算法,可以找到满足 ax0+ny0=1 a x_0 + n y_0=1 ax0+ny0=1 的整数 x0,y0 x_0,y_0 x0,y0。于是 ax0−1=ny0 ax_0-1=ny_0 ax0−1=ny0,所以 ax0≡1(modn) ax_0\equiv 1 \pmod{n} ax0≡1(modn)。令 x=x0  mod  n x=x_0 \bmod n x=x0modn,就找到了符合要求的 xx x。

第二个规律,我们试试反证法。假设可以找到 x1,x2 x_1,x_2 x1,x2 分别满足

  • ax1≡1(modn) a x_1 \equiv 1 \pmod{n} ax1≡1(modn)
  • ax2≡1(modn) a x_2 \equiv 1 \pmod{n} ax2≡1(modn)

我们在第一个等式两边乘以 x2 x_2 x2,会得到
x2ax1≡x2(modn) x_2 a x_1 \equiv x_2 \pmod{n} x2ax1≡x2(modn)

我们在第二个等式两边乘以 x1 x_1 x1,会得到
x1ax2≡x1(modn) x_1 a x_2 \equiv x_1 \pmod{n} x1ax2≡x1(modn)

而模 nn n 的乘法满足交换律和结合律(这里就不证明了),所以
x2ax1≡ax2x1(modn) x_2 a x_1 \equiv ax_2 x_1 \pmod{n} x2ax1≡ax2x1(modn)
ax2x1≡ax1x2(modn) ax_2 x_1 \equiv ax_1 x_2 \pmod{n} ax2x1≡ax1x2(modn)
ax1x2≡x1ax2(modn) ax_1 x_2 \equiv x_1 a x_2 \pmod{n} ax1x2≡x1ax2(modn)

于是 x1≡x2(modn) x_1\equiv x_2 \pmod{n} x1≡x2(modn)。

因此,当我们限制 xx x 满足 0≤x<n0\le x\lt n 0≤x<n 时,能让 ax≡1(modn) a x \equiv 1 \pmod{n} ax≡1(modn) 成立的 xx x 最多只有 11 1 个

用程序来处理

核心逻辑

Python 扩展欧几里得算法 一文中已经提供了扩展欧几里得算法的代码。我们在它的基础上,再加上计算逆元的方法即可 ⬇️

python 复制代码
def extended_euclidean(a, b):
    if (a, b) == (0, 0):
        raise ValueError("a and b cannot be both 0")
    if b == 0:
        return (1, 0, a)
    x, y, g = extended_euclidean(b, a % b)
    return (y, x - a // b * y, g)

def calc_inverse_element(a, n):
    x, _, gcd = extended_euclidean(a, n)
    if gcd == 1:
        return x % n
    return None

添加 GUI 之后的完整程序

豆包 帮我完成了和 Pygame\text{Pygame} Pygame 相关的代码。完整的 Python\text{Python} Python 程序如下 ⬇️

python 复制代码
import pygame

def extended_euclidean(a, b):
    if (a, b) == (0, 0):
        raise ValueError("a and b cannot be both 0")
    if b == 0:
        return (1, 0, a)
    x, y, g = extended_euclidean(b, a % b)
    return (y, x - a // b * y, g)

def calc_inverse_element(a, n):
    x, _, gcd = extended_euclidean(a, n)
    if gcd == 1:
        return x % n
    return None

# ===================== Pygame 初始化 =====================
pygame.init()

WIDTH, HEIGHT = 700, 480
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Inverse Element Calculator")

# 颜色配置
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (220, 220, 220)
BLUE = (50, 100, 200)
RED = (200, 50, 50)
GREEN = (50, 180, 50)

# 字体
font_input = pygame.font.SysFont("Arial", 36)
font_text = pygame.font.SysFont("Arial", 32)
font_result = pygame.font.SysFont("Arial", 38)
font_equation = pygame.font.SysFont("Arial", 32)
font_btn = pygame.font.SysFont("Arial", 34)

# ===================== 布局:全部水平居中,对称美观 =====================
# 输入框组居中
INPUT_X = WIDTH // 2 - 110  # 输入框水平居中
INPUT_WIDTH = 220
INPUT_HEIGHT = 50

# 输入a
input_a_rect = pygame.Rect(INPUT_X, 110, INPUT_WIDTH, INPUT_HEIGHT)
input_a_text = ""
a_active = False

# 输入n
input_n_rect = pygame.Rect(INPUT_X, 180, INPUT_WIDTH, INPUT_HEIGHT)
input_n_text = ""
n_active = False

# 按钮居中
btn_rect = pygame.Rect(WIDTH // 2 - 150, 260, 300, 60)
btn_text = "Calculate"

# 结果
result_text = ""
equation_text = ""
result_color = BLACK

# ===================== 主循环 =====================
running = True
while running:
    screen.fill(WHITE)

    # -------------------- 文字与输入框(居中对齐)--------------------
    # Input a 标签(与输入框垂直居中)
    label_a = font_text.render("Input a:", True, BLACK)
    screen.blit(label_a, (INPUT_X - 120, 118))
    
    # Input n 标签(与输入框垂直居中)
    label_n = font_text.render("Input n:", True, BLACK)
    screen.blit(label_n, (INPUT_X - 120, 188))

    # 绘制输入框a
    pygame.draw.rect(screen, BLUE if a_active else GRAY, input_a_rect, 2)
    screen.blit(font_input.render(input_a_text, True, BLACK), (input_a_rect.x+10, input_a_rect.y+5))

    # 绘制输入框n
    pygame.draw.rect(screen, BLUE if n_active else GRAY, input_n_rect, 2)
    screen.blit(font_input.render(input_n_text, True, BLACK), (input_n_rect.x+10, input_n_rect.y+5))

    # 绘制按钮(居中)
    pygame.draw.rect(screen, BLUE, btn_rect, border_radius=8)
    btn_surface = font_btn.render(btn_text, True, WHITE)
    screen.blit(btn_surface, (btn_rect.centerx - btn_surface.get_width()//2, btn_rect.centery - btn_surface.get_height()//2))

    # 结果文字(居中显示,更美观)
    result_surface = font_result.render(result_text, True, result_color)
    screen.blit(result_surface, (WIDTH // 2 - result_surface.get_width()//2, 350))

    # 公式提示(底部居中)
    formula = font_equation.render(equation_text, True, BLACK)
    screen.blit(formula, (WIDTH // 2 - formula.get_width()//2, 420))

    # ===================== 事件处理 =====================
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        # 鼠标点击
        if event.type == pygame.MOUSEBUTTONDOWN:
            if input_a_rect.collidepoint(event.pos):
                a_active = True
                n_active = False
            elif input_n_rect.collidepoint(event.pos):
                n_active = True
                a_active = False
            elif btn_rect.collidepoint(event.pos):
                result_text = ""
                try:
                    a = int(input_a_text)
                    n = int(input_n_text)
                    if a <= 0 or n <= 0:
                        result_text = "ERROR: Please input positive integers!"
                        result_color = RED
                    else:
                        inv = calc_inverse_element(a, n)
                        if inv is not None:
                            result_text = f"x: {inv}"
                            result_color = GREEN
                            equation_text = f"{a} * {inv} ≡ 1 (mod {n})"
                        else:
                            result_text = f"ERROR: {a} and {n} are not relatively prime, no inverse exists!"
                            result_color = RED
                except ValueError:
                    result_text = "ERROR: Please input valid integers!"
                    result_color = RED
            else:
                a_active = False
                n_active = False

        # 键盘输入
        if event.type == pygame.KEYDOWN:
            if a_active:
                if event.key == pygame.K_BACKSPACE:
                    input_a_text = input_a_text[:-1]
                elif event.unicode.isdigit():
                    input_a_text += event.unicode
            if n_active:
                if event.key == pygame.K_BACKSPACE:
                    input_n_text = input_n_text[:-1]
                elif event.unicode.isdigit():
                    input_n_text += event.unicode

    pygame.display.flip()

pygame.quit()

请将完整的程序保存为 inverse_element_calculator.py。使用如下的命令可以运行 inverse_element_calculator.py

bash 复制代码
python3 inverse_element_calculator.py

初始界面如下图所示 ⬇️

此时需要用户输入 aa a 和 nn n。我输入了 3434 34 和 2323 23,然后点击 Calculate 按钮,就可以计算 aa a 对应的逆元了 ⬇️

由于扩展欧几里得算法很高效,所以即使输入比较大 aa a 和 nn n,也可以立刻算出结果,示例效果如下 ⬇️ ( a=63245986,n=102334155a=63245986,n=102334155 a=63245986,n=102334155)

参考资料

相关推荐
aqi001 小时前
15天学会AI应用开发(十)把文本嵌入模型换成国产模型
人工智能·python·ai编程
金銀銅鐵18 小时前
[Python] 扩展欧几里得算法
python·数学·算法
Duckdblab19 小时前
DuckDB 性能调优终极指南:打造闪电般的分析体验
python
带派擂总19 小时前
Python全栈开发精华版最全合集(包含各种面试题) Day24_异常和错误
python
金銀銅鐵1 天前
n^5 和 n 的个位数是否总相等?
python·数学
aqi001 天前
15天学会AI应用开发(九)利用Chroma持久化向量数据
人工智能·python·大模型·ai编程·ai应用
金銀銅鐵1 天前
借助 Pygame 探索最大公约数的规律
python·数学·游戏