Rabin-Miller Strong Pseudoprime Test Implementation将无效

时间:2013-01-30 20:39:35

标签: python algorithm math

今天一直试图实施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提供的定义有何不同?

5 个答案:

答案 0 :(得分: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
    

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

    # 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才能使此优化正常工作,但我们可以通过在 n True来轻松安排em> = 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. 这里有优化的机会。我们刚刚计算的值 x a 2 0 s (mod n )。所以我们可以立即测试它并保存一个循环迭代:

    # a^(s) = ±1 (mod n)?
    x = pow(a, s, n)
    if x == 1 or x == n - 1:
        continue
    
  6. 在您计算 a 2 j s 的部分中(mod n )这些数字中的每一个都是前一个数字的平方(模 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之前测试小素数的可分性是个好主意。例如,在Rabin's 1977 paper中他说:

      

    在实施算法时,我们采用了一些省力的步骤。首先,我们通过任何素数 p 测试可分性。 N ,其中,说 N = 1000。

  8. 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
    

答案 1 :(得分:4)

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

答案 2 :(得分: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 - 1表达为2^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的示例中,我们可以将其划分一次(所以r = 1),在划分后我们最终得到3(所以s = 3)。

答案 3 :(得分: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

如果您想了解有关素数编程的更多信息,我谦虚地在我的博客上推荐this essay

答案 4 :(得分:1)

你还应该看一下Wikipedia,其中已知的“随机”序列可以给出一个给定素数的保证答案。

  • 如果n&lt; 1,373,653,足以测试a = 2和3;
  • 如果n&lt; 9,080,191,足以测试a = 31和73;
  • 如果n&lt; 4,759,123,141,足以测试a = 2,7和61;
  • 如果n&lt; 2,152,302,898,747,足以测试a = 2,3,5,7和11;
  • 如果n&lt; 3,474,749,660,383,足以测试a = 2,3,5,7,11和13;
  • 如果n&lt; 341,550,071,728,321,足以测试a = 2,3,5,7,11,13和17;