当代码看起来错误时,为什么这个Miller-Rabin程序在Scheme中有效?

时间:2014-04-17 14:47:58

标签: scheme lisp sicp

我正在通过SICP工作。在练习1.28中关于Miller-Rabin测试。我有这个代码,我知道这是错误的,因为它不遵循练习的内容。

(define (fast-prime? n times)           
  (define (even? x)             
    (= (remainder x 2) 0))          
  (define (miller-rabin-test n)         
    (try-it (+ 1 (random (- n 1)))))        
  (define (try-it a)                
    (= (expmod a (- n 1) n) 1))         
  (define (expmod base exp m)           
    (cond ((= exp 0) 1) 
      ((even? exp)              
       (if (and (not (= exp (- m 1))) (= (remainder (square exp) m) 1)) 
         0
         (remainder (square (expmod base (/ exp 2) m)) m)))
      (else         
        (remainder (* base (expmod base (- exp 1) m)) m))))
  (cond ((= times 0) true)          
    ((miller-rabin-test n) (fast-prime? n (- times 1))) 
    (else false)))              

在其中我测试指数的平方是否与1 mod n一致。根据 我所读到的,以及我看到的其他正确实现是错误的。我应该测试一下 整个数字如:

...
        (square 
         (trivial-test (expmod base (/ exp 2) m) m))
...

问题是我测试了这个,有许多素数和大的Carmicheal数, 它似乎给出了正确的答案,虽然有点慢。我不明白为什么这样 似乎工作。

2 个答案:

答案 0 :(得分:3)

你的功能版本“有效”只是因为你很幸运。试试这个实验:评估(fast-prime? 561 3)一百次。根据您的函数选择的随机证人,有时它将返回true,有时它将返回false。当我这样做时,我得到12真和88假,但你可能得到不同的结果,取决于你的随机数发生器。

> (let loop ((k 0) (t 0) (f 0))
    (if (= k 100) (values t f)
      (if (fast-prime? 561 3)
          (loop (+ k 1) (+ t 1) f)
          (loop (+ k 1) t (+ f 1)))))
12
88

我没有SICP在我面前 - 我的副本在家 - 但我可以告诉你正确的方法来进行Miller-Rabin素性测试。

您的expmod功能不正确;没有理由对指数进行平方。这是执行模幂运算的正确函数:

(define (expm b e m) ; modular exponentiation
  (let loop ((b b) (e e) (x 1))
    (if (zero? e) x
      (loop (modulo (* b b) m) (quotient e 2)
            (if (odd? e) (modulo (* b x) m) x)))))

然后是加里·米勒强大的假冒伪劣测试,这是你的try-it测试的强大版本,其中有一个见证 a 证明了每个复合材料的复合性 n ,看起来像这样:

(define (strong-pseudoprime? n a) ; strong pseudoprime base a
  (let loop ((r 0) (s (- n 1)))
    (if (even? s) (loop (+ r 1) (/ s 2))
      (if (= (expm a s n) 1) #t
        (let loop ((r r) (s s))
          (cond ((zero? r) #f)
                ((= (expm a s n) (- n 1)) #t)
                (else (loop (- r 1) (* s 2)))))))))

假设扩展黎曼假设,测试每个 a 从2到 n -1将证明(一个实际的,确定性证明,而不仅仅是对素数的概率估计)素数 n 的素数,或识别至少一个 a ,它是复合物 n 的复合性的见证。迈克尔·拉宾证明,如果 n 是复合的,那么 a 从2到 n -1的至少四分之三是该复合性的见证者,所以测试 k 随机基数证明了,但是没有证明,素数 n 的素数为4 -k 的概率。因此,这种米勒 - 拉宾素性测试的实现:

(define (prime? n k)
  (let loop ((k k))
    (cond ((zero? k) #t)
          ((not (strong-pseudoprime? n (random (+ 2 (- n 3))))) #f)
          (else (loop (- k 1))))))

总是有效:

> (let loop ((k 0) (t 0) (f 0))
    (if (= k 100) (values t f)
      (if (prime? 561 3)
          (loop (+ k 1) (+ t 1) f)
          (loop (+ k 1) t (+ f 1)))))
0
100

我知道你的目的是学习SICP而不是编写素数测试,但如果你对使用素数编程感兴趣,我谦虚地推荐this essay在我的博客上讨论Miller-Rabin测试,等主题。您还应该知道,随机Miller-Rabin有更好的(更快,更不可能报告错误结果)素性测试。

答案 1 :(得分:0)

在我看来,你仍然得到了正确答案,因为在expmod的每次迭代中,你都会检查前一次迭代的条件。您可以尝试使用exp内的display函数调试expmod值。实际上,您的代码与this one没有太大区别。