@comment(Hey, EMACS, this is -*- SCRIBE -*- input)
@make(6001)
@define(s, facecode s)
@set(chapter=1)
@set(page=1)

@PageHeading(even,
             left "@Value(Page)",
             right "6.001")

@PageHeading(odd,
             Left "Problem Set 2",
             right "@value(page)")

@begin(center)
MASSACHUSETTS INSTITUTE OF TECHNOLOGY
Department of Electrical Engineering and Computer Science
6.001 Structure and Interpretation of Computer Programs

Problem Set 2
@end(center)
@blankspace(0.25 in)

@begin(format)
Issued: 20 September 1983@>Due: in Recitation, 28 September 1983

Reading Assignment: Reading: Chapter 1, Sections 1.2 and 1.3
@end(format)

Write up and turn in the following exercises from the text:
@begin(itemize)
Exercise 1-9: Ackerman's Function

Exercise 1-24: Iterative Sum

Exercise 1-25: Products

Exercise 1-26: Accumulate

Exercise 1-28: (F F)

Exercise 1-33: Repeated Application

Exercise 1-35: Cubic Equations
@end(itemize)

@paragraph(LABORATORY ASSIGNMENT: Testing for Primality)

This laboratory assignment deals with the orders of growth in the
number of steps required by some algorithms that test whether a given
number is a prime.  It is based upon Section 1.2.6 of the text.  You
should read that material before beginning work on this assignment.

The following program (from the text) finds the smallest integral
divisor (greater than 1) of a given number n.  It does this in a
straightforward way, by testing n for divisibility by successive
integers starting from 2.
@begin(example)
(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))
@end(example)
We can test whether a number is prime as follows: n is prime if and
only if n is its own smallest divisor.
@begin(example)
(define (prime? n)
  (= n (smallest-divisor n)))
@end(example)
Since SMALLEST-DIVISOR searches between 1 and the square root of n, we
should expect that the number of steps required to identify a number n
as prime will have order of growth the square root of n.

As explained in the text, there is another method for checking
primality, which has logarithmic growth.  This is the probabilistic
test, based on Fermat's Little Theorem.
@begin(example)
(define (fermat-test n)
  (define a (+ 2 (random - n 2)))
  (= (expmod a n n) a))

(define (fast-prime? n times)
  (cond ((= times 0) t)
        ((fermat-test n) (fast-prime? n (- times 1)))
        (else nil)))
@end(example)
Note that FERMAT-TEST fails for N equal to 2.  Think about how you
might fix this.  (The definition, of FAST-PRIME? given above corrects
an error on page 46 in the text.  Ignore that definition given in the
text, along with the pragraph that follows.)

In order to implement the Fermat test, we need a procedure that
computes the exponential of a number modulo another number:
@begin(example)
(define (expmod b e m)
  (cond ((= e 1) b)
        ((divides? 2 e)
         (remainder (square (expmod b (quotient e 2) m))
                    m))
        (else
         (remainder (* b (expmod b (- e 1) m))
                    m))))        
@end(example)
Two minor technical points about the above procedures: We will be
using these to test some very big numbers, and unfortunately Scheme's
ordinary division (/) will not handle numbers larger than about 2^150.
On the other hand, Scheme's integer division (QUOTIENT) will handle
numbers this large, which is why EXPMOD uses this rather than /.
Secondly, the RANDOM procedure built in to Scheme cannot generate
extremely large random numbers.  That is why FERMAT-TEST uses a
procedure BIG-RANDOM, which should be defined as
@begin(example)
   (define (big-random n)
     (random (min n (expt 10 10))))
@end(example)
The procedure can be called with arbitrarily large numbers, but will
only generate numbers less than 10^10.

@paragraph(To Do at the Laboratory)

@b[Part 1:] All the procedurs listed above are installed on the
Chipmunk shared resource manager, and can be loaded onto your floppy
disk.  To do this, follow the instructions givne in the Chipmunk
manual in the section on ``Loading Problem Set Files,'' to load the
code for problem set 2.  (The code for this problem set is
sufficiently short, that we have not separated out the particular
procedures that you are expected to modify, as explained in the
Chipmunk manual.)

Use the program to find the smallest prime divisor of each of the
following numbers:
@begin(example)
              432,881    561    804,413    1,999
@end(example)
Return to the editor and define and transfer to Scheme the following
procedure:
@begin(example)
(define (timed-prime-test n)
  (define start-time (runtime))
  (define found-prime? (prime? n))
  (define elapsed-time (- (runtime) start-time))
  (print n)
  (cond (found-prime? (princ " *** ") (princ elapsed-time))
        (else nil)))
@end(example)
This uses the Scheme primitive procedure RUNTIME, which, when it is
called, returns an integer which gives the amount of time (in
hundredths of a second) that the system has been running.  When you
call TIMED-PRIME-TEST with an integer n, it first prints n on the
terminal and checks to see if n is prime.  If n is prime, it prints
three stars, followed by the time used to perform the test.

Also define the following procedure, which, when called with an odd
integer n, tests the primality of consecutive odd integers starting
with n.  (Obviously, there is no point checking if even integers are
prime.)  The procedure will keep running until you interrupt it by
typing ctrl-G.
@begin(example)
(define (search-for-primes n)
  (timed-prime-test n)
  (search-for-primes (+ n 2)))
@end(example)
Transfer your new procedures to Scheme, and test them.  When you are
satisfied that they are working, use SEARCH-FOR-PRIMES to find the 3
smallest primes that are larger than 100; larger than 1,000; larger
than 10,000; larger than 100,000.

Prepare a chart as follows:
@begin(example)
   Range: 100

   Primes:   <prime1>     <prime2>      <prime3>

   Time1:      _____        _____         _____

   Time2:      _____        _____         _____

   Time3:      _____        _____         _____
@end(example)
with this pattern duplicated for the ranges 1,000; 10,000; and
100,000.  In the spaces marked <prime>, fill in the three smallest
primes you found.  (You will fill in 12 primes in all, 3 in each
range.)  Under each prime, in the spaces marked <Time1>, fill in the
time used by the prime testing procedure to determine that the number
was prime.

@b[Part 2:]
The SMALLEST-DIVISOR procedure is doing lots of needless testing.  For
after it checks to see if the number is divisible by 2, there is no
point checking to see if it is divisible by any larger even numbers.
This suggests that the values used for TEST-DIVISOR should not be 2,
3, 4, 5, 6, 7, ..., but rather 2, 3, 5, 7, 9, ....  To implement this
change, define a procedure NEXT that returns 3 if its input is equal
to 2 and otherwise returns its input plus 2.  Modify SMALLEST-DIVISOR
to use (NEXT TEST-DIVISOR) instead of (+ TEST-DIVISOR 1).

With TIMED-PRIME-TEST using this modified version of TEST-DIVISOR, run
the test for each of the 12 primes listed in your table.  Enter the
times required by the tests in your chart in the slots marked <Time2>.

@b[Part 3:]
Back in the editor, modify TIMED-PRIME-TEST to use FAST-PRIME? in
place of PRIME?.  You can assume that a number is prime if it passes
the Fermat-test for 2 randomly chosen values of A.  Now test each of
the primes as in Part 2 above and enter the required times in the
spaces marked <Time3>.

@b[Part 4:]
Now let's test some really big numbers for primality.
In 1644, the French mathematician Marin Mersenne published the claim
that numbers of the form 2@+[p]@t[-]1 are prime for p in the following list
@begin(example)
               2, 3, 5, 7, 13, 17, 19, 31, 67, 127, 257
@end(example)
and for no other p less than 257.  It turns out that Mersenne missed a
few values of p, and that some of the values in his list do not give
primes.  Use TIMED-PRIME-TEST (modified as in Part 3) to determine
which of the values in Mersenne's list give primes, and which do not.
(Prime numbers of the form 2@+[p]@t[-]1 are known as @i[Mersenne primes].)
Record the time required for each test.  To aid in testing, it will
help to define the following procedure:
@begin(example)
(define (mersenne p) (- (expt 2 p) 1))
@end(example)

@b[Part 5:] Let's try to find which values of p Mersenne missed.  A
straightforward way to do this is to write an interatve procedure that
checks, for all integers p in a given range, whether @a[(mersenne p)]
is prime.  On the other hand, if we blindly, test all integers in a
given range, then we will be doing a lot of needless checking, because
2@+[p]@t[-]1 cannot be prime unless p is prime.  (Sketch of proof:
Show that 2@+[ab]@t[-]1 is divisible by 2@+[a]@t[-]1.)  Thus, you
should write a procedure @a[mersenne-range] that works as follows: For
each p in a given range of integers, the program shoudl first test
whether p is prime.  If so, it should then test whether @a[(mersenne
p)] is prime.  As the program runs, it should print p, @a[(mersenne
p)], and the result of the prime test.

@b[Part 6:] Generalize @a[mersenne-range] to a procedure called
@a[prime-filter-check], which takes as input two integers @a[a] and
@a[b], a predicate called @a[filter], and a procedure of one argument
called @a[term].  For each integer @a[n] in the range from @a[a] to
@a[b] for which @a[(filter n)] true, the program should check whether
@a[(term n)] is prime.  For example, calling the procedure with filter
being @a[prime?] (or @a[fast-prime?]) and term as @a[mersenne], should
do the same thing as the @a[mersenne-range] procedure of part 5.

@paragraph(Post-lab Write-up)

After you are through at the terminal room, you are to write up and
hand in answers to the following questions:

1. What is the smallest divisor of each of the numbers 
that you tested in part 1?

2. Prepare a neat copy of the table you made, listing the 3 primes in
each of the 4 ranges and the corresponding timings for each of the
three primality tests.

3. The number of steps in the first prime test should grow as fast as
the square root of the number being tested.  We should expect
therefore that testing for primes around 1,000 should take about
@radical()10 times as long as testing for primes around 100.  Does your
timing data <Time1> bear this out?  How well does the data for 10,000
and 100,000 support the @radical()n prediction?

4. The modification you made in part 2 was designed to speed up the
prime test by halving the number of test steps.  So you should expect
it to run about twice as fast.  Does your data bear this out?  For
each of the primes in your table, compute the ratio <Time1>/<Time2>.
Does this ratio appear to be constant (i.e., more or less independent
of the number being tested)?  Does the ratio indicate that the new
test runs twice as fast as the old?  If not, what is the actual ratio
and how do you explain the fact that it is different from 2?

5. The number of steps required by the FAST-PRIME? test has O(log n)
growth.  How then would you expect the time to test primes near
100,000 to compare with the time needed to test primes near 100?  How
well does your data bear this out?  Can you explain any discrepancy
you find?

6. Which of the numbers in Mersenne's list do in fact yield primes?

7. In Part 4, how long did your program take to test primality of
2^127 - 1?  Estimate how long the first prime testing algorithm (Part
1) would require to check the primality of this number?  To answer
this question, extrapolate from the data you collected in the TIME1
entries of your table, together with the fact that the number of steps
required grows as @radical()n.  Give the answer, not in seconds, but
in whatever unit seems to most appropriately express the amount of
time required (e.g., minutes, hours, days, ...).  Be sure to explain
how you arrived at your estimate.

8.  Turn in listings of the @a[mersenne-range] and
@a[prime-filter-check] procedures that you wrote in parts 5 and 6.

9.  Do Exercise 1-21, on p.47 of the notes.

10.  Do Exercise 1-22, on p.48 of the notes.