Advertisement
timothy235

sicp-1-2-6-primality-testing

Feb 16th, 2016
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Racket 11.62 KB | None | 0 0
  1. #lang racket
  2.  
  3. ;; 1.21
  4.  
  5. (define (divides? a b)
  6.   (zero? (remainder b a)))
  7. (define (find-divisor n test-divisor)
  8.   (cond [(> (sqr test-divisor) n) n]
  9.         [(divides? test-divisor n) test-divisor]
  10.         [else (find-divisor n (+ test-divisor 1))]))
  11. (define (smallest-divisor n)
  12.   (find-divisor n 2))
  13.  
  14. (smallest-divisor 199)
  15. ;; 199
  16. (smallest-divisor 1999)
  17. ;; 1999
  18. (smallest-divisor 19999)
  19. ;; 7
  20.  
  21. ;; 1.22
  22.  
  23. (define (my-prime? n)
  24.   (= n (smallest-divisor n)))
  25.  
  26. ;; Time my-prime? to verify it is O(sqrt(n)).
  27.  
  28. (define (timed-prime-test n)
  29.   (define (start-prime-test num start-time)
  30.     (cond [(my-prime? num)
  31.            (printf "~a is prime *** ~a ms ~n"
  32.                    num
  33.                    (inexact->exact
  34.                      (truncate
  35.                        (- (current-inexact-milliseconds) start-time))))
  36.            #t]
  37.           [else #f]))
  38.   (start-prime-test n (current-inexact-milliseconds)))
  39.  
  40. (define (find-primes start times)
  41.   ; find and display times many primes, with timing info, beginning at start
  42.   (cond [(zero? times) (newline)]
  43.         [(timed-prime-test start) (find-primes (add1 start) (sub1 times))]
  44.         [else (find-primes (add1 start) times)]))
  45.  
  46. ;; I had to make the numbers big to get noticeable elapsed times.  The starting
  47. ;; numbers go up by a factor of 100.  So with a sqrt(n) time complexity, the
  48. ;; elapsed times should be going up by a factor of 10 each time, which we do see.
  49.  
  50. (find-primes 10000000000000 3)
  51. ;; 10000000000037 is prime *** 115 ms
  52. ;; 10000000000051 is prime *** 106 ms
  53. ;; 10000000000099 is prime *** 115 ms
  54.  
  55. (find-primes 1000000000000000 3)
  56. ;; 1000000000000037 is prime *** 1201 ms
  57. ;; 1000000000000091 is prime *** 1200 ms
  58. ;; 1000000000000159 is prime *** 1203 ms
  59.  
  60. (find-primes 100000000000000000 3)
  61. ;; 100000000000000003 is prime *** 12009 ms
  62. ;; 100000000000000013 is prime *** 12025 ms
  63. ;; 100000000000000019 is prime *** 12140 ms
  64.  
  65. ;; 1.23
  66.  
  67. ;; Redo 1.22 with an improved find-divisor that does not unnecessarily check even
  68. ;; numbers greater than 2.
  69.  
  70. (define (next n) ; return 3 if n is 2 else n + 2
  71.   (if (= n 2) 3 (+ n 2)))
  72.  
  73. (define (new-find-divisor n test-divisor)
  74.   ; same as find-divisor but uses next instead of incrementing by 1
  75.   (cond [(> (sqr test-divisor) n) n]
  76.         [(divides? test-divisor n) test-divisor]
  77.         [else (new-find-divisor n (next test-divisor))]))
  78. (define (new-smallest-divisor n)
  79.   (new-find-divisor n 2))
  80.  
  81. (define (new-my-prime? n)
  82.   (= n (new-smallest-divisor n)))
  83.  
  84. (define (new-timed-prime-test n)
  85.   (define (start-prime-test num start-time)
  86.     (cond [(new-my-prime? num)
  87.            (printf "~a is prime *** ~a ms ~n"
  88.                    num
  89.                    (inexact->exact
  90.                      (truncate
  91.                        (- (current-inexact-milliseconds) start-time))))
  92.            #t]
  93.           [else #f]))
  94.   (start-prime-test n (current-inexact-milliseconds)))
  95.  
  96. (define (new-find-primes start times)
  97.   (cond [(zero? times) (newline)]
  98.         [(new-timed-prime-test start) (new-find-primes (add1 start) (sub1 times))]
  99.         [else (new-find-primes (add1 start) times)]))
  100.  
  101. (new-find-primes 10000000000000 3)
  102. ;; 10000000000037 is prime *** 82 ms
  103. ;; 10000000000051 is prime *** 62 ms
  104. ;; 10000000000099 is prime *** 79 ms
  105.  
  106. (new-find-primes 1000000000000000 3)
  107. ;; 1000000000000037 is prime *** 748 ms
  108. ;; 1000000000000091 is prime *** 748 ms
  109. ;; 1000000000000159 is prime *** 770 ms
  110.  
  111. (new-find-primes 100000000000000000 3)
  112. ;; 100000000000000003 is prime *** 7588 ms
  113. ;; 100000000000000013 is prime *** 7591 ms
  114. ;; 100000000000000019 is prime *** 7603 ms
  115.  
  116. ;; The new elapsed times are a little more than half the old times.  I think this
  117. ;; reflects the fact that timed-prime-test has some unavoidable printing overhead
  118. ;; that the improved my-prime? cannot help.  Nonetheless I think this is a
  119. ;; significant improvement, not a big-O improvement, but a nice leading
  120. ;; coefficient improvement.
  121.  
  122. ;; 1.24
  123.  
  124. ;; Redo 1.22 using fast-prime? instead of my-prime? and check that we get
  125. ;; logarithmic time complexity.
  126.  
  127. (define (expmod base expo m)
  128.   (cond [(zero? expo) 1]
  129.         [(even? expo)
  130.          (remainder (sqr (expmod base (/ expo 2) m)) m)]
  131.         [else (remainder (* base (expmod base (sub1 expo) m)) m)]))
  132.  
  133. (define (fermat-test n)
  134.   (define (try-it a)
  135.     (= (expmod a n n) a))
  136.   (try-it (add1 (random (min 4294967087 ; biggest number accepted by random
  137.                              (sub1 n))))))
  138.  
  139. (define (fast-prime? n times)
  140.   (cond [(zero? times) #t]
  141.         [(fermat-test n) (fast-prime? n (sub1 times))]
  142.         [else #f]))
  143.  
  144. (define (fast-timed-prime-test n)
  145.   ; same as timed-prime-test but uses fast-prime? instead of my-prime?
  146.   (define (start-prime-test num start-time)
  147.     (cond [(fast-prime? num 10) ; run the fermat test ten times
  148.            (printf "~a is prime *** ~a ms ~n"
  149.                    num
  150.                    (inexact->exact
  151.                      (truncate
  152.                        (- (current-inexact-milliseconds) start-time))))
  153.            #t]
  154.           [else #f]))
  155.   (start-prime-test n (current-inexact-milliseconds)))
  156.  
  157. (define (fast-find-primes start times)
  158.   (cond [(zero? times) (newline)]
  159.         [(fast-timed-prime-test start) (fast-find-primes (add1 start) (sub1 times))]
  160.         [else (fast-find-primes (add1 start) times)]))
  161.  
  162. ;; First let's compare with the numbers we used earlier.
  163.  
  164. (fast-find-primes 10000000000000 3)
  165. ;; 10000000000037 is prime *** 0 ms
  166. ;; 10000000000051 is prime *** 0 ms
  167. ;; 10000000000099 is prime *** 0 ms
  168.  
  169. (fast-find-primes 1000000000000000 3)
  170. ;; 1000000000000037 is prime *** 0 ms
  171. ;; 1000000000000091 is prime *** 0 ms
  172. ;; 1000000000000159 is prime *** 0 ms
  173.  
  174. (fast-find-primes 100000000000000000 3)
  175. ;; 100000000000000003 is prime *** 0 ms
  176. ;; 100000000000000013 is prime *** 0 ms
  177. ;; 100000000000000019 is prime *** 0 ms
  178.  
  179. ;; So we got the same primes but this time basically instantaneously.
  180.  
  181. ;; Now we will have to use huge numbers to register any elapsed time at all.  With
  182. ;; log running time, doubling the number of digits in a number should double the
  183. ;; elapsed time.
  184.  
  185. (fast-find-primes (inexact->exact 1e25) 3)
  186. ;; 10000000000000000905969697 is prime *** 2 ms
  187. ;; 10000000000000000905969749 is prime *** 1 ms
  188. ;; 10000000000000000905969823 is prime *** 1 ms
  189.  
  190. (fast-find-primes (inexact->exact 1e50) 3)
  191. ;; 100000000000000007629769841091887003294964970946821 is prime *** 4 ms
  192. ;; 100000000000000007629769841091887003294964970947027 is prime *** 4 ms
  193. ;; 100000000000000007629769841091887003294964970947049 is prime *** 4 ms
  194.  
  195. (fast-find-primes (inexact->exact 1e100) 3)
  196. ;; 100000000000000001590289110975991804683608085639452813897813275577478387
  197. ;; 72170381060813469985856815251 is prime *** 15 ms
  198. ;; 100000000000000001590289110975991804683608085639452813897813275577478387
  199. ;; 72170381060813469985856815363 is prime *** 14 ms
  200. ;; 100000000000000001590289110975991804683608085639452813897813275577478387
  201. ;; 72170381060813469985856815719 is prime *** 17 ms
  202.  
  203. (fast-find-primes (inexact->exact 1e200) 3)
  204. ;; 999999999999999969733122212510361659474503275455023626482417509503468484
  205. ;; 355540755341963384047062518680275124159738824081821357343682784846393850
  206. ;; 41047239877871023591066789981811181813306167128854888513 is prime *** 69 ms
  207. ;; 999999999999999969733122212510361659474503275455023626482417509503468484
  208. ;; 355540755341963384047062518680275124159738824081821357343682784846393850
  209. ;; 41047239877871023591066789981811181813306167128854888901 is prime *** 73 ms
  210. ;; 999999999999999969733122212510361659474503275455023626482417509503468484
  211. ;; 355540755341963384047062518680275124159738824081821357343682784846393850
  212. ;; 41047239877871023591066789981811181813306167128854890043 is prime *** 71 ms
  213.  
  214. ;; The run times did not double each time, they go up by a factor
  215. ;; of 4, but any constant factor increase is still evidence of logarithmic
  216. ;; time.  So our test is successful.
  217.  
  218. ;; Note how (inexact->exact 1e200) is not 1 followed by 200 zeroes but is rather a
  219. ;; 200-digit number beginning with many 9's.  I guess that's due to some machine
  220. ;; limitation.
  221.  
  222. ;; 1.25
  223.  
  224. ;; Alyssa's expmod code does not go modulo after each squaring operation, but only
  225. ;; takes one remainder at the end.  Note that when we use expmod in the fermat
  226. ;; test, the number n we are testing for primality is the exponent, and this
  227. ;; number can be very large.  So the intermediate numbers in Alyssa's expmod
  228. ;; process will be super huge and probably cause some kind of register overflow
  229. ;; error.
  230.  
  231. ;; 1.26
  232.  
  233. ;; expmod is a recursive procedure and it's time complexity is the number of nodes
  234. ;; in the recursion tree.  By doubling the number of recursive calls, Louis's
  235. ;; expmod turns the recursion tree from a single branch with log(n) nodes, into a
  236. ;; full binary tree with n nodes.
  237.  
  238. ;; 1.27
  239.  
  240. (define carmichael '(561 1105 1729 2465 2821 6601))
  241.  
  242. (define (passes-fermat? n)
  243.   (for/and ([a (in-range 2 n)])
  244.            (= (expmod a n n) a)))
  245.  
  246. (passes-fermat? 5)
  247. ;; #t
  248. (passes-fermat? 6)
  249. ;; #f
  250. (passes-fermat? 7)
  251. ;; #t
  252. (passes-fermat? 8)
  253. ;; #f
  254. (passes-fermat? 9)
  255. ;; #f
  256.  
  257. (for/and ([n carmichael]) (passes-fermat? n))
  258. ;; #t
  259.  
  260. (for/or ([n carmichael]) (my-prime? n))
  261. ;; #f
  262.  
  263. ;; So the carmichael numbers are non-primes that fool the fermat test.
  264.  
  265. ;; 1.28
  266.  
  267. ;; the Miller-Rabin primality test
  268.  
  269. (define (special-expmod base expo m)
  270.   ; same as expmod but immediately returns 0 on encountering
  271.   ; a non-trivial square root of unity
  272.   (cond [(zero? expo) 1]
  273.         [(even? expo)
  274.          (define u (special-expmod base (/ expo 2) m))
  275.          (define u-squared (remainder (sqr u) m))
  276.          (if (and (= u-squared 1)
  277.                   (> u 1)
  278.                   (< u (sub1 m)))
  279.            0
  280.            u-squared)]
  281.         [else (remainder (* base (special-expmod base (sub1 expo) m)) m)]))
  282.  
  283. (define (miller-rabin-prime? n [times 10]) ; run 10 times by default
  284.   (define (try-it a)
  285.     (= (special-expmod a (sub1 n) n) 1))
  286.   (if (< n 10)
  287.     (member n '(2 3 5 7))
  288.     (for/and ([i (in-range times)])
  289.              ; do not test 0, 1, or n - 1
  290.              (try-it (+ 2 (random (min 4294967087 (- n 3))))))))
  291.  
  292. ;; easy tests
  293.  
  294. (miller-rabin-prime? 10)
  295. ;; #f
  296. (miller-rabin-prime? 11)
  297. ;; #t
  298. (miller-rabin-prime? 12)
  299. ;; #f
  300. (miller-rabin-prime? 13)
  301. ;; #t
  302. (miller-rabin-prime? 14)
  303. ;; #f
  304. (miller-rabin-prime? 15)
  305. ;; #f
  306. (miller-rabin-prime? 16)
  307. ;; #f
  308. (miller-rabin-prime? 17)
  309. ;; #t
  310.  
  311. ;; big primes from earlier problems
  312.  
  313. (miller-rabin-prime? 10000000000037)
  314. ;; #t
  315. (miller-rabin-prime? 10000000000051)
  316. ;; #t
  317. (miller-rabin-prime? 10000000000099)
  318. ;; #t
  319. (miller-rabin-prime? 1000000000000037)
  320. ;; #t
  321. (miller-rabin-prime? 1000000000000091)
  322. ;; #t
  323. (miller-rabin-prime? 1000000000000159)
  324. ;; #t
  325. (miller-rabin-prime? 100000000000000003)
  326. ;; #t
  327. (miller-rabin-prime? 100000000000000013)
  328. ;; #t
  329. (miller-rabin-prime? 100000000000000019)
  330. ;; #t
  331.  
  332. ;; close but no cigar
  333.  
  334. (miller-rabin-prime? (* 10000000000037 10000000000051))
  335. ;; #f
  336. (miller-rabin-prime? (* 1000000000000091 1000000000000159))
  337. ;; #f
  338. (miller-rabin-prime? (* 100000000000000003 100000000000000019))
  339. ;; #f
  340.  
  341. ;; the toughies
  342.  
  343. (for/or ([n carmichael])
  344.         (miller-rabin-prime? n))
  345. ;; #f
  346.  
  347. ;; lastly let's time it on some pretty big primes
  348.  
  349. (time (miller-rabin-prime?  100000000000000007629769841091887003294964970946821))
  350. ;; cpu time: 16 real time: 9 gc time: 0
  351. ;; #t
  352. (time (miller-rabin-prime?  100000000000000007629769841091887003294964970947027))
  353. ;; cpu time: 15 real time: 8 gc time: 0
  354. ;; #t
  355. (time (miller-rabin-prime?  100000000000000007629769841091887003294964970947049))
  356. ;; cpu time: 0 real time: 5 gc time: 0
  357. ;; #t
  358.  
  359. ;; So Miller-Rabin looks like a very efficient and trustworthy primality checker.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement