今天一直试图实施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提供的定义有何不同?
答案 0 :(得分:6)
我将在其他答案中注明以下几点,但将它们放在一起似乎很有用。
在
部分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表示法,那么您必须在代码的这一部分中交换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
在
行中for i in range(k):
变量i
未被使用:将这些变量命名为_
是常规的。
您选择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一样。)
正如其他回复中所述,您误解了MathWorld的文章。当它说“ n 通过测试”时,意味着“ n 通过基础 a ”的测试。关于素数的区别事实是它们通过了所有基础的测试。所以当你发现 a s = 1(mod n )时,你应该做的就是绕圈循环并选择下一个基地进行测试。
# a^(s) = 1 (mod n)?
x = pow(a, s, n)
if x == 1:
continue
这里有优化的机会。我们刚刚计算的值 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
在您计算 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
在尝试Miller-Rabin之前测试小素数的可分性是个好主意。例如,在Rabin's 1977 paper中他说:
在实施算法时,我们采用了一些省力的步骤。首先,我们通过任何素数 p 测试可分性。 N ,其中,说 N = 1000。
把所有这些放在一起:
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 = 1
和s = 3
。
但上面的代码找到了别的东西。它以r = 6
开头,因此r % 2 == 0
。最初,s = 0
因此,在一次迭代后,我们有s = 1
和r = 3
。但是现在r % 2 != 0
并且循环终止。
我们最终得到的s = 1
和r = 3
明显不正确:2^r * s = 8
。
您不应在循环中更新s
。相反,您应该计算除以2的次数(这将是r
),并且除法之后的结果将是s
。在n = 7
,n - 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,其中已知的“随机”序列可以给出一个给定素数的保证答案。