Generating Pi in Clojure

Generating PI in Clojure

Posted by Uncle Bob on Wednesday, August 05, 2009

The SICP book shows a cute algorithm for computing PI using infinite sequences (lazy evaluation). I have adapted it for Clojure and modified it to use Java’s BigDecimal class, so that I can generate arbitrary precision.

First we define a function that generates an “infinite” list of terms of the form 1/1 + 1/3 – 1/5 + 1/7 – ... This is a well known series that very slowly converges on Pi. Notice that I’m using the ‘M’ suffix to denote BigDecimal, and I’m restricting the precision to 300 places. Notice also that I’m using lazy-seq. This function delays the execution of the ‘cons’ term until that part of the list is actually used. This allows the list to appear to be infinite.

(defn pi-summands [n]   (lazy-seq     (cons (with-precision 300 (/ 1M n))       (map - (pi-summands (+ n 2)))))) 

Next we have two utility functions. The first multiplies the terms of a list by a scale factor. The other adds two lists together producing a list of sums. If the input lists are ‘infinite’, the output lists will also be ‘infinite’.

(defn scale-stream [stream factor]   (map (fn [x] (* x factor)) stream))  (defn add-streams [p q]   (map +  p q))

partial-sums takes a list of terms produces a list of the sums of those terms. Given [a b c] it produces [a a+b a+b+c]. And, again, the lists can be infinite.

(defn partial-sums [s]   (lazy-seq     (cons (first s) (add-streams (next s) (partial-sums s)))))

pi-stream mutiplies the partial-sums of the pi-summands by 4. Turning the summing sequence into 4/1 + 4/3 – 4/5…

(def pi-stream   (scale-stream (partial-sums (pi-summands 1)) 4))

A simple squaring function.

(defn square [s] (* s s))

Euler’s transform is a fancy way to average three terms of a +/- sequence. It can dramatically increase the precision of an otherwise slowly converging sequence. The euler-transform function produces a sequence of results from the pi-stream. Again, note the constraint I place on BigDecimal precision.

(defn euler-transform [s]   (let [s0 (nth s 0)         s1 (nth s 1)         s2 (nth s 2)]     (lazy-seq       (cons (with-precision 300 (- s2 (/ (square (- s2 s1))         (+ s0 (* -2 s1) s2))))         (euler-transform (next s))))))

It turns out that you can use Euler’s transform on it’s own results to further increase precision. The make-tableau function arranges the reapplication of a transform so that it can be repeatedly applied to itself.

(defn make-tableau [transform s]   (lazy-seq     (cons s (make-tableau transform (transform s)))))

And now we grab the first element of each repetition.

(defn accelerated-sequence [transform s]   (map first (make-tableau transform s)))

A little utility to print the elements of a list.

(defn print-list [s] (doseq [n s] (println n)))

And now finally we print the first 150 iterations of the accelerated euler-transformation of the pi sequence.

(print-list (take 150   (accelerated-sequence euler-transform pi-stream)))

The 150th result (the first two lines of which are accurate) 3.14159265358979323846264338327950288419716939 93751058209749445923078164062862089986280348253 25256352154500957038098311477352066337027814620 46050836612955894790973622135628850706785335611 34661971615744147647325427374216175843967838084 09640502874355811103562027043480098593797655792 36344321165429010895M

Comments