4

有人告诉我,Frobenius 伪素数算法的运行时间是 Miller-Rabin 素数检验的三倍,但分辨率却是七倍。因此,如果前者运行 10 次,后者运行 30 次,两者的运行时间相同,但前者的分析能力提高了 233%。在试图找出如何进行测试时,最后发现了以下带有算法的论文:

Frobenius 伪素检验的简单推导

尝试实现下面的算法,但程序从不打印出数字。请更熟悉数学符号或算法的人验证发生了什么?

编辑 1:下面的代码添加了更正,但compute_wm_wm1缺少实现。有人可以从算法的角度解释递归定义吗?这对我来说不是“点击”。

编辑2:错误代码已被删除,并在compute_wm_wm1下面添加了该功能的实现。它似乎有效,但可能需要进一步优化才能实用。

from random import SystemRandom
from fractions import gcd
random = SystemRandom().randrange

def find_prime_number(bits, test):
    number = random((1 << bits - 1) + 1, 1 << bits, 2)
    while True:
        for _ in range(test):
            if not frobenius_pseudoprime(number):
                break
        else:
            return number
        number += 2

def frobenius_pseudoprime(integer):
    assert integer & 1 and integer >= 3
    a, b, d = choose_ab(integer)
    w1 = (a ** 2 * extended_gcd(b, integer)[0] - 2) % integer
    m = (integer - jacobi_symbol(d, integer)) >> 1
    wm, wm1 = compute_wm_wm1(w1, m, integer)
    if w1 * wm != 2 * wm1 % integer:
        return False
    b = pow(b, (integer - 1) >> 1, integer)
    return b * wm % integer == 2

def choose_ab(integer):
    a, b = random(1, integer), random(1, integer)
    d = a ** 2 - 4 * b
    while is_square(d) or gcd(2 * d * a * b, integer) != 1:
        a, b = random(1, integer), random(1, integer)
        d = a ** 2 - 4 * b
    return a, b, d

def is_square(integer):
    if integer < 0:
        return False
    if integer < 2:
        return True
    x = integer >> 1
    seen = set([x])
    while x * x != integer:
        x = (x + integer // x) >> 1
        if x in seen:
            return False
        seen.add(x)
    return True

def extended_gcd(n, d):
    x1, x2, y1, y2 = 0, 1, 1, 0
    while d:
        n, (q, d) = d, divmod(n, d)
        x1, x2, y1, y2 = x2 - q * x1, x1, y2 - q * y1, y1
    return x2, y2

def jacobi_symbol(n, d):
    j = 1
    while n:
        while not n & 1:
            n >>= 1
            if d & 7 in {3, 5}:
                j = -j
        n, d = d, n
        if n & 3 == 3 == d & 3:
            j = -j
        n %= d
    return j if d == 1 else 0

def compute_wm_wm1(w1, m, n):
    a, b = 2, w1
    for shift in range(m.bit_length() - 1, -1, -1):
        if m >> shift & 1:
            a, b = (a * b - w1) % n, (b * b - 2) % n
        else:
            a, b = (a * a - 2) % n, (a * b - w1) % n
    return a, b

print('Probably prime:\n', find_prime_number(300, 10))
4

1 回答 1

14

由于不熟悉符号,您似乎完全误解了该算法。

def frobenius_pseudoprime(integer):
    assert integer & 1 and integer >= 3
    a, b, d = choose_ab(integer)
    w1 = (a ** 2 // b - 2) % integer

那来自于线

W 0 ≡ 2 (mod n) 和 W 1 ≡ a 2 b -1 - 2 (mod n)

但是这里的 b -1并不意味着1/b,而是modulo的模逆,即带有 的整数。您可以通过扩展欧几里得算法的连分数展开或等效地,但计算量稍多一些,最容易找到这样的 a 。由于您可能不熟悉连分数,我推荐后者。bncb·c ≡ 1 (mod n)cb/n

    m = (integer - d // integer) // 2

来自

n - (Δ/n) = 2m

并将Jacobi 符号误解为分数/除法(诚然,我在这里将其显示为更像分数,但由于该站点不支持 LaTeX 渲染,我们将不得不这样做)。Jacobi符号是Legendre符号的推广——表示相同——它表示一个数是否是模一个奇素数的二次余数(如果n是一个模数二次余数,p即有一个k和不是 的倍数,那么,如果是 的倍数,则,否则k^2 ≡ n (mod p)np(n/p) = 1np(n/p) = 0(n/p) = -1)。Jacobi 符号解除了“分母”为奇质数的限制,并允许任意奇数作为“分母”。它的值是所有素数除法n(根据多重性)具有相同“分子”的勒让德符号的乘积。更多关于这一点,以及如何在链接文章中有效地计算雅可比符号。该行应正确读取

m = (integer - jacobi_symbol(d,integer)) // 2

以下几行我完全看不懂,从逻辑上讲,这里应该遵循使用递归计算 W m和 W m+1

W 2j ≡ W j 2 - 2 (mod n)

W 2j+1 ≡ W j W j+1 - W 1 (mod n)

围绕 PDF 的公式 (11) 给出了使用该递归计算所需值的有效方法。

    w_m0 = w1 * 2 // m % integer
    w_m1 = w1 * 2 // (m + 1) % integer
    w_m2 = (w_m0 * w_m1 - w1) % integer

该函数的其余部分几乎是正确的,当然,由于之前的误解,它现在得到了错误的数据。

    if w1 * w_m0 != 2 * w_m2:

这里的(不)等式应该是模数integer,即if (w1*w_m0 - 2*w_m2) % integer != 0

        return False
    b = pow(b, (integer - 1) // 2, integer)
    return b * w_m0 % integer == 2

但是请注意,如果n是素数,则

b^((n-1)/2) ≡ (b/n) (mod n)

其中(b/n)是勒让德(或雅可比)符号(对于素数“分母”,雅可比符号勒让德符号),因此b^((n-1)/2) ≡ ±1 (mod n). 因此,您可以将其用作额外检查,如果 W m不是 2 或n-2n则不能是素数,如果b^((n-1)/2) (mod n)不是 1 或,也不能是n-1

可能b^((n-1)/2) (mod n)首先计算并检查它是 1 还是n-1一个好主意,因为如果该检查失败(顺便说一下,这是欧拉伪素测试),您不再需要另一个同样昂贵的计算,并且如果它成功,很可能无论如何你都需要计算它。

关于更正,它们似乎是正确的,除了一个我之前忽略的小故障可能更糟:

if w1 * wm != 2 * wm1 % integer:

这仅将模数应用于2 * wm1

关于 W j的递归,我认为最好用一个有效的实现来解释,首先是为了便于复制和粘贴:

def compute_wm_wm1(w1,m,n):
    a, b = 2, w1
    bits = int(log(m,2)) - 2
    if bits < 0:
        bits = 0
    mask = 1 << bits
    while mask <= m:
        mask <<= 1
    mask >>= 1
    while mask > 0:
        if (mask & m) != 0:
            a, b = (a*b-w1)%n, (b*b-2)%n
        else:
            a, b = (a*a-2)%n, (a*b-w1)%n
        mask >>= 1
    return a, b

然后在两者之间进行解释:

def compute_wm_wm1(w1,m,n):

我们需要 W 1的值、所需数字的索引以及将模数作为输入的数字。W 0的值始终为 2,因此我们不需要它作为参数。

称它为

wm, wm1 = compute_wm_wm1(w1,m,integer)

in frobenius_pseudoprime(除了:不是一个好名字,大多数返回的数字True都是实数)。

    a, b = 2, w1

我们分别初始化W 0a和W 1。在每一点,保存 W j的值和W j+1的值,其中是到目前为止消耗的位的值。例如,使用、和的值如下:babjmm = 13jab

consumed remaining  j    a    b
           1101     0   w_0  w_1
   1        101     1   w_1  w_2
   11        01     3   w_3  w_4
   110        1     6   w_6  w_7
   1101            13  w_13  w_14

这些位是从左到右消耗的,所以我们必须找到第一个设置位m并将我们的“指针”放在它之前

    bits = int(log(m,2)) - 2
    if bits < 0:
        bits = 0
    mask = 1 << bits

我从计算的对数中减去了一点,只是为了完全确定我们不会被浮点错误所迷惑(顺便说一下,使用log限制你到最多 1024 位的数字,大约 308 个十进制数字;如果你想处理更大的数字,你必须以不同的方式找到以 2 为底的对数m,使用log是最简单的方法,它只是一个概念证明,所以我在这里使用它)。

    while mask <= m:
        mask <<= 1

移动掩码直到它大于m,所以设置的位就在m第一个设置位之前。然后向后移动一个位置,因此我们指向该位。

    mask >>= 1
    while mask > 0:
        if (mask & m) != 0:
            a, b = (a*b-w1)%n, (b*b-2)%n

如果设置了下一位,则消耗位的初始部分的值mj2*j+1,因此我们需要的 W 序列的下一个值是 W 2j+1 fora和 W 2j+2 for b。由上述递归公式,

W_{2j+1} = W_j * W_{j+1} - W_1 (mod n)
W_{2j+2} = W_{j+1}^2 - 2 (mod n)

因为a是 W jb是 W j+1a变成(a*b - W_1) % nb变成(b * b - 2) % n

        else:
            a, b = (a*a-2)%n, (a*b-w1)%n

如果下一位未设置,则消耗位的初始部分的值mj变为2*j,因此a变为 W 2j = (W j 2 - 2) (mod n),b变为 W 2j+1 = (W j * W j+1 - W 1 ) (mod n)。

        mask >>= 1

将指针移动到下一位。当我们经过最后一位时,mask变为 0 并且循环结束。消耗位的初始部分m现在是所有m位,所以值当然是m。然后我们可以

    return a, b

一些补充说明:

def find_prime_number(bits, test):
    while True:
        number = random(3, 1 << bits, 2)
        for _ in range(test):
            if not frobenius_pseudoprime(number):
                break
        else:
            return number

在较大的数字中,素数不太频繁,因此仅选择随机数可能需要多次尝试才能找到一个。如果您选择一个随机数并按顺序检查候选者,您可能会更快地找到一个素数(或可能的素数)。

另一点是,像 Frobenius 测试这样的测试发现例如 3 的倍数是合数的代价不成比例。在使用这样的测试(或 Miller-Rabin 测试,或 Lucas 测试,或 Euler 测试,......)之前,您绝对应该进行一些试验划分以清除大多数复合材料并仅在以下情况下进行工作它有一个值得的战斗机会。

哦,该is_square函数不准备处理小于 2 的参数,除零错误潜伏在那里,

def is_square(integer):
    if integer < 0:
        return False
    if integer < 2:
        return True
    x = integer // 2

应该有帮助。

于 2012-05-18T19:16:26.143 回答