ef04c7b3d0b8

Problem 10, Miller-Rabin, etc
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Mon, 11 Apr 2016 14:40:47 +0000
parents d4b651730553
children a66997c0fad3
branches/tags (none)
files .lispwords Makefile euler.lisp package.lisp primes.lisp utils.lisp

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.lispwords	Mon Apr 11 14:40:47 2016 +0000
@@ -0,0 +1,1 @@
+(1 repeat)
--- a/Makefile	Mon Apr 11 14:40:32 2016 +0000
+++ b/Makefile	Mon Apr 11 14:40:47 2016 +0000
@@ -1,7 +1,7 @@
 .PHONY: test
 
 quickutils.lisp: make-utilities.lisp
-	sbcl --noinform --load make-utilities.lisp  --eval '(quit)'
+	sbcl-rlwrap --noinform --load make-utilities.lisp  --eval '(quit)'
 
 test:
-	sbcl --noinform --load run-tests.lisp  --eval '(quit)'
+	sbcl-rlwrap --noinform --load run-tests.lisp  --eval '(quit)'
--- a/euler.lisp	Mon Apr 11 14:40:32 2016 +0000
+++ b/euler.lisp	Mon Apr 11 14:40:47 2016 +0000
@@ -145,6 +145,11 @@
                       :when (pythagorean-triplet-p a b c)
                       :do (return-from search (* a b c)))))))
 
+(defun problem-10 ()
+  (loop :for p :in (primes-below 2000000)
+        :sum p))
+
+
 ;;;; Tests
 (def-suite :euler)
 (in-suite :euler)
@@ -158,6 +163,7 @@
 (test p7 (is (= 104743 (problem-7))))
 (test p8 (is (= 23514624000 (problem-8))))
 (test p9 (is (= 31875000 (problem-9))))
+(test p10 (is (= 142913828922 (problem-10))))
 
 
 ; (run! :euler)
--- a/package.lisp	Mon Apr 11 14:40:32 2016 +0000
+++ b/package.lisp	Mon Apr 11 14:40:47 2016 +0000
@@ -2,6 +2,7 @@
   (:use #:cl #:euler.quickutils)
   (:export
     #:random-exclusive
+    #:repeat
     #:dividesp))
 
 (defpackage #:euler
--- a/primes.lisp	Mon Apr 11 14:40:32 2016 +0000
+++ b/primes.lisp	Mon Apr 11 14:40:47 2016 +0000
@@ -1,5 +1,11 @@
 (in-package #:euler)
 
+(define-constant carmichael-numbers ; from oeis
+  '(561 1105 1729 2465 2821 6601 8911 10585 15841 29341 41041 46657 52633
+    62745 63973 75361 101101 115921 126217 162401 172081 188461 252601 278545
+    294409 314821 334153 340561 399001 410041 449065 488881 512461)
+  :test #'equal)
+
 (defun prime-factorization (n)
   "Return the prime factors of `n`."
   ;; from http://www.geeksforgeeks.org/print-all-prime-factors-of-a-given-number/
@@ -16,57 +22,142 @@
     (nreverse result)))
 
 
-(defun fermat-prime-p (p &optional (tests 10))
-  "Return whether `p` might be prime.
+(defun expmod (base exp m)
+  "Return base^exp % m quickly."
+  ;; From SICP and
+  ;; https://rosettacode.org/wiki/Miller%E2%80%93Rabin_primality_test#Common_Lisp
+  ;;
+  ;; We want to avoid bignums as much as possible.  This computes (base^exp % m)
+  ;; without having to deal with huge numbers by taking advantage of the fact
+  ;; that:
+  ;;
+  ;;     (x * y) % m
+  ;;
+  ;; is equivalent to:
+  ;;
+  ;;     ((x % m) * (y % m)) % m
+  ;;
+  ;; So for the cases where `exp` is even, we can split base^exp into an x and
+  ;; y both equal to base^(exp/2) and use the above trick to handle them
+  ;; separately.  Even better, we can just compute it once and square it.
+  ;;
+  ;; We also make it tail recursive by keeping a running accumulator:
+  ;;
+  ;;    base^exp * acc
+  (labels
+      ((recur (base exp acc)
+         (cond
+           ((zerop exp) acc)
+           ((evenp exp)
+            (recur (rem (square base) m)
+                   (/ exp 2)
+                   acc))
+           (t
+            (recur base
+                   (1- exp)
+                   (rem (* base acc) m))))))
+    (recur base exp 1)))
+
+
+(defun fermat-prime-p (n &optional (tests 10))
+  "Return whether `n` might be prime.
 
   Checks `tests` times.
 
-  If `t` is returned, `p` might be prime.  If `nil` is returned it is definitely
+  If `t` is returned, `n` might be prime.  If `nil` is returned it is definitely
   composite.
 
   "
-  (if (or (zerop p) (= 1 p))
-    nil
-    (flet ((fermat-check (a)
-             (= 1 (mod (expt a (1- p)) p))))
-      (loop :repeat tests
-            :when (not (fermat-check (random-exclusive 0 p)))
-            :do (return nil)
-            :finally (return t)))))
+  (flet ((fermat-check (a)
+           (= (expmod a n n) a)))
+    (loop :repeat tests
+          :when (not (fermat-check (random-exclusive 0 n)))
+          :do (return nil)
+          :finally (return t))))
+
+(defun factor-out (n factor)
+  "Factor the all the `factor`s out of `n`.
+
+  Turns `n` into:
+
+    factor^e * d
+
+  where `d` is no longer divisible by `n`, and returns `e` and `d`.
+
+  "
+  (loop :for d = n :then (/ d factor)
+        :for e = 0 :then (1+ e)
+        :while (dividesp d factor)
+        :finally (return (values e d))))
+
+(defun miller-rabin-prime-p (n &optional (k 10))
+  "Return whether `n` might be prime.
+
+  If `t` is returned, `n` is probably prime.
+  If `nil` is returned, `n` is definitely composite.
 
-(defun brute-force-prime-p (p)
-  "Return (slowly) whether `p` is prime."
+  "
+  ;; https://rosettacode.org/wiki/Miller%E2%80%93Rabin_primality_test#Common_Lisp
   (cond
-    ((or (= p 0) (= p 1)) nil)
-    ((= p 2) t)
-    ((evenp p) nil)
-    (t (loop :for divisor :from 3 :to (sqrt p)
-          :when (dividesp p divisor)
+    ((< n 2) nil)
+    ((< n 4) t)
+    ((evenp n) nil)
+    (t (multiple-value-bind (r d)
+           (factor-out (1- n) 2)
+         (flet ((strong-liar-p (a)
+                  (let ((x (expmod a d n)))
+                    (or (= x 1)
+                        (loop :repeat r
+                              :for y = x :then (expmod y 2 n)
+                              :when (= y (1- n))
+                              :do (return t))))))
+           (loop :repeat k
+                 :for a = (random-exclusive 1 (1- n))
+                 :always (strong-liar-p a)))))))
+
+(defun brute-force-prime-p (n)
+  "Return (slowly) whether `n` is prime."
+  (cond
+    ((or (= n 0) (= n 1)) nil)
+    ((= n 2) t)
+    ((evenp n) nil)
+    (t (loop :for divisor :from 3 :to (sqrt n)
+          :when (dividesp n divisor)
           :do (return nil)
           :finally (return t)))))
 
+(defun primep (n)
+  "Return (less slowly) whether `n` is prime."
+  (cond
+    ;; short-circuit a few edge/common cases
+    ((< n 2) nil)
+    ((= n 2) t)
+    ((evenp n) nil)
+    ((< n 100000) (brute-force-prime-p n))
+    (t (miller-rabin-prime-p n))))
+
 
 (defun primes-below (n)
-  "Return (slowly) the prime numbers less than `n`."
+  "Return the prime numbers less than `n`."
   (cond
     ((<= n 2) (list))
     ((= n 3) (list 2))
     (t (cons 2 (loop :for i :from 3 :by 2 :below n
-                     :when (brute-force-prime-p i)
+                     :when (primep i)
                      :collect i)))))
 
 (defun primes-upto (n)
-  "Return (slowly) the prime numbers less than or equal to `n`."
+  "Return the prime numbers less than or equal to `n`."
   (primes-below (1+ n)))
 
 
 (defun nth-prime (n)
-  "Return (slowly) the `n`th prime number."
+  "Return the `n`th prime number."
   (if (= n 1)
     2
     (loop :with seen = 1
           :for i :from 3 :by 2
-          :when (brute-force-prime-p i)
+          :when (primep i)
           :do (progn
                 (incf seen)
                 (when (= seen n)
--- a/utils.lisp	Mon Apr 11 14:40:32 2016 +0000
+++ b/utils.lisp	Mon Apr 11 14:40:47 2016 +0000
@@ -8,3 +8,8 @@
   "Return whether `n` is evenly divisible by `divisor`."
   (zerop (mod n divisor)))
 
+
+(defmacro repeat (n &body body)
+  "Repeat `body` `n` times."
+  `(dotimes (,(gensym) ,n)
+     ,@body))