Search code examples
algorithmschemelispprimessicp

SICP exercise 1.28: false negatives in the Miller-Rabin test


Exercise 1.28. One variant of the Fermat test that cannot be fooled is called the Miller-Rabin test (Miller 1976; Rabin 1980). This starts from an alternate form of Fermat's Little Theorem, which states that if n is a prime number and a is any positive integer less than n, then a raised to the (n - 1)st power is congruent to 1 modulo n. To test the primality of a number n by the Miller-Rabin test, we pick a random number a < n and raise a to the (n - 1)st power modulo n using the expmod procedure. However, whenever we perform the squaring step in expmod, we check to see if we have discovered a ''nontrivial square root of 1 modulo n,'' that is, a number not equal to 1 or n - 1 whose square is equal to 1 modulo n. It is possible to prove that if such a nontrivial square root of 1 exists, then n is not prime. It is also possible to prove that if n is an odd number that is not prime, then, for at least half the numbers a < n, computing a^(n-1) in this way will reveal a nontrivial square root of 1 modulo n. (This is why the Miller-Rabin test cannot be fooled.) Modify the expmod procedure to signal if it discovers a nontrivial square root of 1, and use this to implement the Miller-Rabin test with a procedure analogous to fermat-test. Check your procedure by testing various known primes and non-primes. Hint: One convenient way to make expmod signal is to have it return 0.

(define (fast-prime? n)
  (define (fast-prime-iter n counter)
    (cond ((= counter 1) #t) ; There is no need to check 1
          ((miller-rabin-test n counter)
           (fast-prime-iter n (- counter 1)))
          (else 
            (newline)
            (display counter)
            #f)))
  (fast-prime-iter n (- n 2)))

(define (miller-rabin-test n a)
  (define (expmod base exp m)
    (cond ((= exp 0) 1)
          ((even? exp)
           (nontrivial-square-root?
             (remainder (square (expmod base (/ exp 2) m))
                        m)))
          (else
            (remainder (* base (expmod base (- exp 1) m))
                       m))))
  (= (expmod a (- n 1) n) 1))

(define (nontrivial-square-root? val)
  (if (= val 1)
    0
    val))

My idea is to filter out those so-called "nontrivial square roots of 1 modulo n" with the procedure nontrivial-square-root?. A 0 is returned if (remainder (square (expmod base (/ exp 2) m)) m) is 1, in which case the square of (expmod base (/ exp 2) m) must be equal to 1 modulo n (this is because m always equals n), making it a nontrivial square root.

While nontrivial-square-root? does filter out carmichael numbers such as 561, 1105, 1729, 2465, 2821 and 6601, prime numbers such as 7 and 13 are also reported to be composite.

What causes these false negatives?


Solution

  • The important part of the quote marked with bold text:

    However, whenever we perform the squaring step in expmod, we check to see if we have discovered a ''nontrivial square root of 1 modulo n,'' that is, a number not equal to 1 or n - 1 whose square is equal to 1 modulo n

    So before you square and take the remainder you have to check that the argument is not 1 or n - 1. This occurs, e.g., if you call (miller-rabin-test 5 3). Working the recursion out you notice that there is a call (nontrivial-square-root? (remainder (square 4) 5)) which evaluates to (nontrivial-square-root? 1). However, 5 can still be prime because 4 is 5 - 1.

    So in the squaring part you can, e.g., call a following function:

    (define (sqrmod-with-check val n)
      (let ((sqrmod (remainder (square val) n)))
        (cond ((or (= val (- n 1)) (= val 1)) sqrmod)
              ((= sqrmod 1) 0)
              (else sqrmod))))
    

    where the arguments are the expmod call and m. This does the square and remainder for you except in the case we have found a nontrivial square root of 1 modulo n, when it returns 0. I divided it to three conditions, instead of two, just because of readability.