5

今天一直在尝试实施 Rabin-Miller 强伪素测试。

使用Wolfram Mathworld作为参考,第 3-5 行很好地总结了我的代码。

但是,当我运行该程序时,它(有时)说素数(甚至低,例如 5、7、11)不是素数。我已经查看了很长时间的代码,但无法找出问题所在。

为了获得帮助,我查看了这个站点以及许多其他站点,但大多数都使用另一个定义(可能相同,但由于我是这种数学的新手,我看不到同样明显的联系)。

我的代码:

import random

def RabinMiller(n, k):

    # obviously not prime
    if n < 2 or n % 2 == 0:
        return False

    # special case        
    if n == 2:
        return True

    s = 0
    r = n - 1

    # factor n - 1 as 2^(r)*s
    while r % 2 == 0:
        s = s + 1
        r = r // 2  # floor

    # k = accuracy
    for i in range(k):
        a = random.randrange(1, n)

        # a^(s) mod n = 1?
        if pow(a, s, n) == 1:
            return True

        # a^(2^(j) * s) mod n = -1 mod n?
        for j in range(r):
            if pow(a, 2**j*s, n) == -1 % n:
                return True

    return False

print(RabinMiller(7, 5))

这与 Mathworld 给出的定义有何不同?

4

5 回答 5

6

1. 对您的代码的注释

我将在下面提出的一些观点在其他答案中得到了说明,但将它们放在一起似乎很有用。

  1. 在该部分

    s = 0
    r = n - 1
    
    # factor n - 1 as 2^(r)*s
    while r % 2 == 0:
        s = s + 1
        r = r // 2  # floor
    

    您已经交换了rs的角色:您实际上已将n - 1 分解为 2 s r。如果您想坚持使用 MathWorld 表示法,那么您必须在这部分代码中交换r和:s

    # factor n - 1 as 2^(r)*s, where s is odd.
    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    
  2. 在行

    for i in range(k):
    

    该变量i未使用:通常命名此类变量_

  3. 你选择一个介于 1 和n - 1之间的随机基数:

    a = random.randrange(1, n)
    

    这是它在 MathWorld 文章中所说的,但那篇文章是从数学家的角度写的。事实上,选择基数 1 是没有用的,因为 1 s = 1 (mod n ) 你会浪费试验。同样,选择底数n - 1 是没有用的,因为s是奇数,因此 ( n - 1) s = -1 (mod n )。数学家不必担心浪费的试验,但程序员会这样做,所以改为:

    a = random.randrange(2, n - 1)
    

    n需要至少为 4 才能使此优化起作用,但是我们可以通过在nTrue = 3时返回函数顶部来轻松安排它,就像您为n = 2 所做的那样。)

  4. 正如其他回复中所述,您误解了 MathWorld 文章。当它说“ n通过测试”时,它意味着“ n通过了基数a的测试”。关于素数的显着事实是它们通过了所有碱基的测试。因此,当您发现a s = 1 (mod n ) 时,您应该做的是绕过循环并选择下一个要测试的基础。

    # a^(s) = 1 (mod n)?
    x = pow(a, s, n)
    if x == 1:
        continue
    
  5. 这里有优化的机会。我们刚刚计算的值x2 0 s (mod n )。所以我们可以立即对其进行测试并为自己节省一次循环迭代:

    # a^(s) = ±1 (mod n)?
    x = pow(a, s, n)
    if x == 1 or x == n - 1:
        continue
    
  6. 在计算2 j s (mod n )部分中,这些数字中的每一个都是前一个数字的平方 (modulo n )。当您可以将先前的值平方时,从头开始计算每个值是浪费的。所以你应该把这个循环写成:

    # a^(2^(j) * s) = -1 (mod n)?
    for _ in range(r - 1):
        x = pow(x, 2, n)
        if x == n - 1:
            break
    else:
        return False
    
  7. 在尝试 Miller-Rabin 之前测试小素数的可分性是个好主意。例如,在拉宾 1977 年的论文中,他说:

    在实现算法时,我们结合了一些省力的步骤。首先,我们测试任何素数p < N的可分性,例如N = 1000。

2. 修改代码

把所有这些放在一起:

from random import randrange

small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] # etc.

def probably_prime(n, k):
    """Return True if n passes k rounds of the Miller-Rabin primality
    test (and is probably prime). Return False if n is proved to be
    composite.

    """
    if n < 2: return False
    for p in small_primes:
        if n < p * p: return True
        if n % p == 0: return False
    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    for _ in range(k):
        a = randrange(2, n - 1)
        x = pow(a, s, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True
于 2013-01-31T01:21:31.590 回答
4

除了 Omri Barel 所说的之外,您的 for 循环也存在问题。true如果您找到a通过测试的人,您将返回。但是,所有人a都必须通过测试n才能成为可能的素数。

于 2013-01-30T21:19:29.880 回答
2

我想知道这段代码:

# factor n - 1 as 2^(r)*s
while r % 2 == 0:
    s = s + 1
    r = r // 2  # floor

让我们来吧n = 7。所以n - 1 = 6。我们可以表示n - 12^1 * 3。在这种情况下r = 1s = 3

但是上面的代码发现了别的东西。它以 开头r = 6,所以r % 2 == 0。最初,s = 0所以在一次迭代之后,我们有s = 1r = 3。但是现在r % 2 != 0,循环终止了。

我们最终得到s = 1r = 3这显然是不正确的:2^r * s = 8.

您不应该s在循环中更新。相反,您应该计算可以除以 2 的次数(这将是r),除以之后的结果将是s。在 , 的示例中n = 7n - 1 = 6我们可以将其除一次 (so r = 1),除法后我们得到 3 (so s = 3)。

于 2013-01-30T21:05:39.863 回答
2

这是我的版本:

# miller-rabin pseudoprimality checker

from random import randrange

def isStrongPseudoprime(n, a):
    d, s = n-1, 0
    while d % 2 == 0:
        d, s = d/2, s+1
    t = pow(a, d, n)
    if t == 1:
        return True
    while s > 0:
        if t == n - 1:
            return True
        t, s = pow(t, 2, n), s - 1
    return False

def isPrime(n, k):
    if n % 2 == 0:
        return n == 2
    for i in range(1, k):
        a = randrange(2, n)
        if not isStrongPseudoprime(n, a):
            return False
    return True

如果你想了解更多关于素数编程的知识,我谦虚地在我的博客上推荐这篇文章。

于 2013-01-30T21:52:50.427 回答
1

您还应该查看Wikipedia,其中已知的“随机”序列给出了给定素数的有保证的答案。

  • 如果 n < 1,373,653,测试 a = 2 和 3 就足够了;
  • 如果 n < 9,080,191,测试 a = 31 和 73 就足够了;
  • 如果 n < 4,759,123,141,则测试 a = 2、7 和 61 就足够了;
  • 如果 n < 2,152,302,898,747,则测试 a = 2、3、5、7 和 11 就足够了;
  • 如果 n < 3,474,749,660,383,则测试 a = 2、3、5、7、11 和 13 就足够了;
  • 如果 n < 341,550,071,728,321,则测试 a = 2、3、5、7、11、13 和 17 就足够了;
于 2013-01-31T20:48:55.707 回答