Exercise 1.22 of SICP

Exercise 1.22: Most Lisp implementations include a primitive called runtime (my implementation of scheme doesn’t. I define a work around) that returns an integer that specifies the amount of time the system has been running (measured, for example, in microseconds). The following timed-prime-test procedure, when called with an integer n, prints n and checks to see if n is prime. If n is prime, the procedure prints three asterisks followed by the amount of time used in performing the test.

(define (runtime) (time->seconds (current-time)))
(define (timed-prime-test n)
  (newline)
  (display n)
  (start-prime-test n (runtime)))
(define (start-prime-test n start-time)
  (if (prime? n)
	  (report-prime (- (runtime) start-time))))
(define (report-prime elapsed-time)
  (display " *** ")
  (display elapsed-time))

Using this procedure, write a procedure search-for-primes that checks the primality of consecutive odd integers in a specified range. Use your procedure to find the three smallest primes larger than 1000; larger than 10,000; larger than 100,000; larger than 1,000,000. Note the time needed to test each prime. Since the testing algorithm has order of growth of \( \Theta(\sqrt n) \), you should expect that testing for primes around 10,000 should take about \( \sqrt 10 \) times as long as testing for primes around 1000. Do your timing data bear this out? How well do the data for 100,000 and 1,000,000 support the \( \sqrt n \) prediction? Is your result compatible with the notion that programs on your machine run in time proportional to the number of steps required for the computation?

> (timed-prime-test 68720001023) 
68720001023 *** .4850001335144043

> (timed-prime-test 70368760954879) 
70368760954879 *** 16.844000101089478

Since this exercise is a bit dated testing numbers on the order 106 for primality doesn’t give my system any noticeable workout. I test my ranges starting from 1010.

> (search-for-primes (expt 10 10) (- (expt 10 11) 1)) 

10000000019 *** .14100003242492676
10000000033 *** .1399998664855957
10000000061 *** .14000010490417483 

According to the theoretical calculations the numbers on the order of 1011 should take \( 0.44 \approx 0.14 \cdot \sqrt{10} \) seconds.

Error: 17%

> (search-for-primes (expt 10 11) (- (expt 10 12) 1)) 

100000000003 *** .5310001373291016
100000000019 *** .5320000648498535
100000000057 *** .54699993133544923 

\( 1.71 \approx 0.54 \cdot \sqrt{10} \) seconds.

> (search-for-primes (expt 10 12) (- (expt 10 13) 1))

1000000000039 *** 1.7660000324249268
1000000000061 *** 1.75
1000000000063 *** 1.76500010490417483

\( 5.57 \approx 1.76 \cdot \sqrt{10} \) seconds.

Error: 2%

> (search-for-primes (expt 10 13) (- (expt 10 14) 1))

10000000000037 *** 5.672000169754028
10000000000051 *** 5.672000169754028
10000000000099 *** 5.65599989891052253

It seems that \( \Theta(\sqrt n) \) is indeed a good predictor of the time it takes to check for primality of the algorithm.

(define (search-for-primes beg end)
  (define (search beg end num-primes)
	(cond ((= num-primes 3) num-primes)
		  ((> beg end) num-primes)
		  ((even? beg) (search (+ beg 1) end num-primes))
		  ((prime? beg) (timed-prime-test beg) 
						(search (+ beg 2) end (+ 1 num-primes)))
		  (else
			(search (+ beg 2) end num-primes))))
  (search beg end 0))

(define (runtime) (time->seconds (current-time)))
(define (square x) (* x x))
(define (smallest-divisor n)
  (find-divisor n 2))
(define (find-divisor n test-divisor)
  (cond ((> (square test-divisor) n) n)
		((divides? test-divisor n) test-divisor)
		(else (find-divisor n (+ test-divisor 1)))))
(define (divides? a b)
  (= (remainder b a) 0))
(define (prime? n)
  (= n (smallest-divisor n)))

(define (timed-prime-test n)
  (newline)
  (display n)
  (start-prime-test n (runtime)))
(define (start-prime-test n start-time)
  (if (prime? n)
	  (report-prime (- (runtime) start-time))))
(define (report-prime elapsed-time)
  (display " *** ")
  (display elapsed-time))