From posting-system@google.com Tue Nov 13 18:07:39 2001 Date: Tue, 13 Nov 2001 15:07:34 -0800 From: oleg-at-pobox.com Newsgroups: comp.lang.scheme X-comment: added the second algorithm. Subject: Re: arbritary precision rationals References: <87668rm2wf.fsf@stuff.gen.nz> Message-ID: <7eb8ac3e.0111131507.650021e5@posting.google.com> Status: OR In penance for the thread about the probability of two random integers being mutually prime, I humbly submit a message only about Scheme. This article shows a not-so-naive realization of the Eratosthenes Sieve. The code computes a sequence of prime numbers not exceeding a given number N, rather fast. The sophistication of the algorithm is in avoiding wasting time and space on even numbers. This message also shows the example of computing with exact rationals. The Eratosthenes sieve algorithm is described in http://mathworld.wolfram.com/SieveofEratosthenes.html In the naive implementation, we would use a vector v of size N+1. v[i] is a boolean indicating the status of integer i, i=0..N. v[i] is #f if integer i has been eliminated (dropped through the sieve). Initially v is set to #t, with the exception v[0]=v[1]=#f (obviously 0 and 1 are not primes). We start by locating the first i (greater than the last found prime) so that v[i] is #t. We add v[i] to the list of found primes, and eliminate all multiples of i. That is, we set v[2i], v[3i], etc. to #f. We repeat the process until there is nothing to eliminate. Since all primes except 2 are odd, it is wasteful to even consider even numbers. Now, we will interpret v[i] as an indication of the status of a number n(i) = (2*i)+3. Initially, all elements of the vector are set to #t. We locate the first i in the yet-to-be-examined portion of v so that v[i] is #t. Such i corresponds to a number p(i)= (2*i)+3. We add this number to the list of found primes and eliminate all its multiples. Because we do not consider even numbers, we need to eliminate only odd multiples, (2k+1)*(2*i+3). k=1.... That is, we need to set v[ ( (2k+1)*(2*i+3) - 3 ) / 2 ] to #f or v[ k*p(i) + i] = #f The algorithm becomes (define (prime-sieve N) (let* ((max-index (quotient (- N 3) 2)) (v (make-vector (+ 1 max-index) #t))) ; i is the current index on the tape ; primes is the list of found primes, in reverse order (let loop ((i 0) (primes '(2))) (cond ((> i max-index) (reverse primes)) ((vector-ref v i) (let ((prime (+ i i 3))) ; newly found prime (do ((j (+ i prime) (+ j prime))) ((> j max-index)) (vector-set! v j #f)) (loop (+ 1 i) (cons prime primes)))) (else (loop (+ 1 i) primes)))))) This algorithm has no multiplications (let alone divisions) in the main loop. George Kangas from the Univ. of Kansas has pointed out that if we allow general multiplications, we can eliminate redundant assignments of the elements of v to #f. Once we detected the prime p(i)= (2*i)+3, we can start the elimination pass, the setting of v[ k*p(i) + i] = #f starting with k=i+1 rather than k=1. That is, we start the elimination from the element corresponding to prime^2. All composite elements of v before that index have already been eliminated in the earlier passes. He furthermore pointed out that once the element corresponding to prime^2 is outside the array `v', no productive eliminations are possible. Therefore, we can stop and scan the vector `v' for elements still containing #t, collecting the corresponding primes. (define (prime-sieve N) (let* ((max-index (max 0 (quotient (- N 3) 2))) (v (make-vector (+ 1 max-index) #t))) (let loop ((i 0)) ; i is the current index on the tape (cond ((vector-ref v i) (let* ((prime (+ i i 3)) ; newly found prime (p2i (+ i (* (+ i 1) prime)))) ; the index of prime^2 (do ((j p2i (+ j prime))) ((> j max-index)) (vector-set! v j #f)) (if (> p2i max-index) ; no point to continue elimination, so ; we collect the primes, scanning backwards (let collect ((i max-index) (primes '())) (cond ((negative? i) (cons 2 primes)) ((vector-ref v i) (collect (- i 1) (cons (+ i i 3) primes))) (else (collect (- i 1) primes)))) (loop (+ 1 i))))) (else (loop (+ 1 i))))))) For example, (prime-sieve 10000) gives the list of all prime numbers not exceeding 10000. The first 20 are > (take 20 (prime-sieve 10000)) (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71) and the last 10 are > (reverse (take 10 (reverse (prime-sieve 10000)))) (9887 9901 9907 9923 9929 9931 9941 9949 9967 9973) It took only 0.579 CPU seconds for a Petite Chez _interpreter_ to compute all primes not exceeding 1 million (Pentium IV 2GHz). The second algorithm takes only 0.378 CPU seconds. BTW, there are 78498 such primes, well in agreement with the approximation of the prime count function, pi(1000000) = (/ 1000000 (log 1000000)) = 72382 As an illustration of exact rational arithmetics (implemented in Gambit), let's compute P(K) = Prod{ (1 - 1/p[i]^2), i=1..K} where p[i] is the i-th prime number (p[1]=2). (define (approx N) (exact->inexact (apply * (map (lambda (p) (- 1 (/ 1 (* p p)))) (prime-sieve N))))) All computation except the final exact->inexact is exact. > (approx 10) .626938775510204 > (approx 100) .6090337253995166 > (approx 200) .6083818935932919 > (approx 1000) .6080043073061253 > (approx 10000) .6079330691140551 We have argued that P(K) converges to 1/Z[2] = 6/Pi^2: > (let ((pi (* 4 (atan 1)))) (/ 6 (* pi pi))) .6079271018540267 That does seem to be the case indeed.