Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #lang racket
- ;; 1.21
- (define (divides? a b)
- (zero? (remainder b a)))
- (define (find-divisor n test-divisor)
- (cond [(> (sqr test-divisor) n) n]
- [(divides? test-divisor n) test-divisor]
- [else (find-divisor n (+ test-divisor 1))]))
- (define (smallest-divisor n)
- (find-divisor n 2))
- (smallest-divisor 199)
- ;; 199
- (smallest-divisor 1999)
- ;; 1999
- (smallest-divisor 19999)
- ;; 7
- ;; 1.22
- (define (my-prime? n)
- (= n (smallest-divisor n)))
- ;; Time my-prime? to verify it is O(sqrt(n)).
- (define (timed-prime-test n)
- (define (start-prime-test num start-time)
- (cond [(my-prime? num)
- (printf "~a is prime *** ~a ms ~n"
- num
- (inexact->exact
- (truncate
- (- (current-inexact-milliseconds) start-time))))
- #t]
- [else #f]))
- (start-prime-test n (current-inexact-milliseconds)))
- (define (find-primes start times)
- ; find and display times many primes, with timing info, beginning at start
- (cond [(zero? times) (newline)]
- [(timed-prime-test start) (find-primes (add1 start) (sub1 times))]
- [else (find-primes (add1 start) times)]))
- ;; I had to make the numbers big to get noticeable elapsed times. The starting
- ;; numbers go up by a factor of 100. So with a sqrt(n) time complexity, the
- ;; elapsed times should be going up by a factor of 10 each time, which we do see.
- (find-primes 10000000000000 3)
- ;; 10000000000037 is prime *** 115 ms
- ;; 10000000000051 is prime *** 106 ms
- ;; 10000000000099 is prime *** 115 ms
- (find-primes 1000000000000000 3)
- ;; 1000000000000037 is prime *** 1201 ms
- ;; 1000000000000091 is prime *** 1200 ms
- ;; 1000000000000159 is prime *** 1203 ms
- (find-primes 100000000000000000 3)
- ;; 100000000000000003 is prime *** 12009 ms
- ;; 100000000000000013 is prime *** 12025 ms
- ;; 100000000000000019 is prime *** 12140 ms
- ;; 1.23
- ;; Redo 1.22 with an improved find-divisor that does not unnecessarily check even
- ;; numbers greater than 2.
- (define (next n) ; return 3 if n is 2 else n + 2
- (if (= n 2) 3 (+ n 2)))
- (define (new-find-divisor n test-divisor)
- ; same as find-divisor but uses next instead of incrementing by 1
- (cond [(> (sqr test-divisor) n) n]
- [(divides? test-divisor n) test-divisor]
- [else (new-find-divisor n (next test-divisor))]))
- (define (new-smallest-divisor n)
- (new-find-divisor n 2))
- (define (new-my-prime? n)
- (= n (new-smallest-divisor n)))
- (define (new-timed-prime-test n)
- (define (start-prime-test num start-time)
- (cond [(new-my-prime? num)
- (printf "~a is prime *** ~a ms ~n"
- num
- (inexact->exact
- (truncate
- (- (current-inexact-milliseconds) start-time))))
- #t]
- [else #f]))
- (start-prime-test n (current-inexact-milliseconds)))
- (define (new-find-primes start times)
- (cond [(zero? times) (newline)]
- [(new-timed-prime-test start) (new-find-primes (add1 start) (sub1 times))]
- [else (new-find-primes (add1 start) times)]))
- (new-find-primes 10000000000000 3)
- ;; 10000000000037 is prime *** 82 ms
- ;; 10000000000051 is prime *** 62 ms
- ;; 10000000000099 is prime *** 79 ms
- (new-find-primes 1000000000000000 3)
- ;; 1000000000000037 is prime *** 748 ms
- ;; 1000000000000091 is prime *** 748 ms
- ;; 1000000000000159 is prime *** 770 ms
- (new-find-primes 100000000000000000 3)
- ;; 100000000000000003 is prime *** 7588 ms
- ;; 100000000000000013 is prime *** 7591 ms
- ;; 100000000000000019 is prime *** 7603 ms
- ;; The new elapsed times are a little more than half the old times. I think this
- ;; reflects the fact that timed-prime-test has some unavoidable printing overhead
- ;; that the improved my-prime? cannot help. Nonetheless I think this is a
- ;; significant improvement, not a big-O improvement, but a nice leading
- ;; coefficient improvement.
- ;; 1.24
- ;; Redo 1.22 using fast-prime? instead of my-prime? and check that we get
- ;; logarithmic time complexity.
- (define (expmod base expo m)
- (cond [(zero? expo) 1]
- [(even? expo)
- (remainder (sqr (expmod base (/ expo 2) m)) m)]
- [else (remainder (* base (expmod base (sub1 expo) m)) m)]))
- (define (fermat-test n)
- (define (try-it a)
- (= (expmod a n n) a))
- (try-it (add1 (random (min 4294967087 ; biggest number accepted by random
- (sub1 n))))))
- (define (fast-prime? n times)
- (cond [(zero? times) #t]
- [(fermat-test n) (fast-prime? n (sub1 times))]
- [else #f]))
- (define (fast-timed-prime-test n)
- ; same as timed-prime-test but uses fast-prime? instead of my-prime?
- (define (start-prime-test num start-time)
- (cond [(fast-prime? num 10) ; run the fermat test ten times
- (printf "~a is prime *** ~a ms ~n"
- num
- (inexact->exact
- (truncate
- (- (current-inexact-milliseconds) start-time))))
- #t]
- [else #f]))
- (start-prime-test n (current-inexact-milliseconds)))
- (define (fast-find-primes start times)
- (cond [(zero? times) (newline)]
- [(fast-timed-prime-test start) (fast-find-primes (add1 start) (sub1 times))]
- [else (fast-find-primes (add1 start) times)]))
- ;; First let's compare with the numbers we used earlier.
- (fast-find-primes 10000000000000 3)
- ;; 10000000000037 is prime *** 0 ms
- ;; 10000000000051 is prime *** 0 ms
- ;; 10000000000099 is prime *** 0 ms
- (fast-find-primes 1000000000000000 3)
- ;; 1000000000000037 is prime *** 0 ms
- ;; 1000000000000091 is prime *** 0 ms
- ;; 1000000000000159 is prime *** 0 ms
- (fast-find-primes 100000000000000000 3)
- ;; 100000000000000003 is prime *** 0 ms
- ;; 100000000000000013 is prime *** 0 ms
- ;; 100000000000000019 is prime *** 0 ms
- ;; So we got the same primes but this time basically instantaneously.
- ;; Now we will have to use huge numbers to register any elapsed time at all. With
- ;; log running time, doubling the number of digits in a number should double the
- ;; elapsed time.
- (fast-find-primes (inexact->exact 1e25) 3)
- ;; 10000000000000000905969697 is prime *** 2 ms
- ;; 10000000000000000905969749 is prime *** 1 ms
- ;; 10000000000000000905969823 is prime *** 1 ms
- (fast-find-primes (inexact->exact 1e50) 3)
- ;; 100000000000000007629769841091887003294964970946821 is prime *** 4 ms
- ;; 100000000000000007629769841091887003294964970947027 is prime *** 4 ms
- ;; 100000000000000007629769841091887003294964970947049 is prime *** 4 ms
- (fast-find-primes (inexact->exact 1e100) 3)
- ;; 100000000000000001590289110975991804683608085639452813897813275577478387
- ;; 72170381060813469985856815251 is prime *** 15 ms
- ;; 100000000000000001590289110975991804683608085639452813897813275577478387
- ;; 72170381060813469985856815363 is prime *** 14 ms
- ;; 100000000000000001590289110975991804683608085639452813897813275577478387
- ;; 72170381060813469985856815719 is prime *** 17 ms
- (fast-find-primes (inexact->exact 1e200) 3)
- ;; 999999999999999969733122212510361659474503275455023626482417509503468484
- ;; 355540755341963384047062518680275124159738824081821357343682784846393850
- ;; 41047239877871023591066789981811181813306167128854888513 is prime *** 69 ms
- ;; 999999999999999969733122212510361659474503275455023626482417509503468484
- ;; 355540755341963384047062518680275124159738824081821357343682784846393850
- ;; 41047239877871023591066789981811181813306167128854888901 is prime *** 73 ms
- ;; 999999999999999969733122212510361659474503275455023626482417509503468484
- ;; 355540755341963384047062518680275124159738824081821357343682784846393850
- ;; 41047239877871023591066789981811181813306167128854890043 is prime *** 71 ms
- ;; The run times did not double each time, they go up by a factor
- ;; of 4, but any constant factor increase is still evidence of logarithmic
- ;; time. So our test is successful.
- ;; Note how (inexact->exact 1e200) is not 1 followed by 200 zeroes but is rather a
- ;; 200-digit number beginning with many 9's. I guess that's due to some machine
- ;; limitation.
- ;; 1.25
- ;; Alyssa's expmod code does not go modulo after each squaring operation, but only
- ;; takes one remainder at the end. Note that when we use expmod in the fermat
- ;; test, the number n we are testing for primality is the exponent, and this
- ;; number can be very large. So the intermediate numbers in Alyssa's expmod
- ;; process will be super huge and probably cause some kind of register overflow
- ;; error.
- ;; 1.26
- ;; expmod is a recursive procedure and it's time complexity is the number of nodes
- ;; in the recursion tree. By doubling the number of recursive calls, Louis's
- ;; expmod turns the recursion tree from a single branch with log(n) nodes, into a
- ;; full binary tree with n nodes.
- ;; 1.27
- (define carmichael '(561 1105 1729 2465 2821 6601))
- (define (passes-fermat? n)
- (for/and ([a (in-range 2 n)])
- (= (expmod a n n) a)))
- (passes-fermat? 5)
- ;; #t
- (passes-fermat? 6)
- ;; #f
- (passes-fermat? 7)
- ;; #t
- (passes-fermat? 8)
- ;; #f
- (passes-fermat? 9)
- ;; #f
- (for/and ([n carmichael]) (passes-fermat? n))
- ;; #t
- (for/or ([n carmichael]) (my-prime? n))
- ;; #f
- ;; So the carmichael numbers are non-primes that fool the fermat test.
- ;; 1.28
- ;; the Miller-Rabin primality test
- (define (special-expmod base expo m)
- ; same as expmod but immediately returns 0 on encountering
- ; a non-trivial square root of unity
- (cond [(zero? expo) 1]
- [(even? expo)
- (define u (special-expmod base (/ expo 2) m))
- (define u-squared (remainder (sqr u) m))
- (if (and (= u-squared 1)
- (> u 1)
- (< u (sub1 m)))
- 0
- u-squared)]
- [else (remainder (* base (special-expmod base (sub1 expo) m)) m)]))
- (define (miller-rabin-prime? n [times 10]) ; run 10 times by default
- (define (try-it a)
- (= (special-expmod a (sub1 n) n) 1))
- (if (< n 10)
- (member n '(2 3 5 7))
- (for/and ([i (in-range times)])
- ; do not test 0, 1, or n - 1
- (try-it (+ 2 (random (min 4294967087 (- n 3))))))))
- ;; easy tests
- (miller-rabin-prime? 10)
- ;; #f
- (miller-rabin-prime? 11)
- ;; #t
- (miller-rabin-prime? 12)
- ;; #f
- (miller-rabin-prime? 13)
- ;; #t
- (miller-rabin-prime? 14)
- ;; #f
- (miller-rabin-prime? 15)
- ;; #f
- (miller-rabin-prime? 16)
- ;; #f
- (miller-rabin-prime? 17)
- ;; #t
- ;; big primes from earlier problems
- (miller-rabin-prime? 10000000000037)
- ;; #t
- (miller-rabin-prime? 10000000000051)
- ;; #t
- (miller-rabin-prime? 10000000000099)
- ;; #t
- (miller-rabin-prime? 1000000000000037)
- ;; #t
- (miller-rabin-prime? 1000000000000091)
- ;; #t
- (miller-rabin-prime? 1000000000000159)
- ;; #t
- (miller-rabin-prime? 100000000000000003)
- ;; #t
- (miller-rabin-prime? 100000000000000013)
- ;; #t
- (miller-rabin-prime? 100000000000000019)
- ;; #t
- ;; close but no cigar
- (miller-rabin-prime? (* 10000000000037 10000000000051))
- ;; #f
- (miller-rabin-prime? (* 1000000000000091 1000000000000159))
- ;; #f
- (miller-rabin-prime? (* 100000000000000003 100000000000000019))
- ;; #f
- ;; the toughies
- (for/or ([n carmichael])
- (miller-rabin-prime? n))
- ;; #f
- ;; lastly let's time it on some pretty big primes
- (time (miller-rabin-prime? 100000000000000007629769841091887003294964970946821))
- ;; cpu time: 16 real time: 9 gc time: 0
- ;; #t
- (time (miller-rabin-prime? 100000000000000007629769841091887003294964970947027))
- ;; cpu time: 15 real time: 8 gc time: 0
- ;; #t
- (time (miller-rabin-prime? 100000000000000007629769841091887003294964970947049))
- ;; cpu time: 0 real time: 5 gc time: 0
- ;; #t
- ;; So Miller-Rabin looks like a very efficient and trustworthy primality checker.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement