Calculating π in Clojure (Salamin-Brent)
Took a shot at implementing a PI digit generator in Clojure using a ‘fast’ algorithm.
It seemed like a decent enough excercise to try and understand something about performance in Clojure.
It seemed like a decent enough excercise to try and understand something about performance in Clojure.
MacBook Pro – Intel Core 2 Duo 2.26 GHz – 4GB RAM
Java(TM) SE Runtime Environment (build 1.6.0_15-b03-219)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02-90, mixed mode)
Clojure 1.1.0-alpha-SNAPSHOT (Aug 20 2009) git commit f1f5ad40984d46bdc314090552b76471ee2b8d01
Clojure matches the performance of Java in this example.
The Clojure code :
(import 'java.lang.Math)
(import 'java.math.MathContext)
(import 'java.math.BigDecimal)
(defn sb-pi [places]
"Calculates PI digits using the Salamin-Brent algorithm
and Java's BigDecimal class."
(let [digits (.intValue (+ 10 places)) ;; add some guard digits
round-mode BigDecimal/ROUND_DOWN]
(letfn [(big-sqrt[#^BigDecimal num]
"Calculates square root using Newton's method."
(letfn [(big-sqrt-int
[#^BigDecimal num #^BigDecimal x0 #^BigDecimal x1]
"aux function for calculating square root"
(let [#^BigDecimal x0new x1
#^BigDecimal x1new (-> num (.divide x0new digits round-mode))
#^BigDecimal xsum (+ x1new x0new)
#^BigDecimal x1tot (-> xsum (.divide 2M digits round-mode))]
(if (= x0 x1)
x1tot
(recur num x1 x1tot))))]
(big-sqrt-int
num 0M (BigDecimal/valueOf
(Math/sqrt (. num doubleValue))))))
(sb-pi-int [#^BigDecimal a #^BigDecimal b
#^BigDecimal t #^BigDecimal x #^BigDecimal y]
"aux function for calculating PI"
(let
[#^BigDecimal y1 a
#^BigDecimal absum (+ a b)
#^BigDecimal a1 (-> absum (.divide 2M digits round-mode))
#^BigDecimal b1 (big-sqrt (* b y1))
#^BigDecimal ydiff (- y1 a1)
#^BigDecimal t1 (- t (* x ydiff ydiff))
#^BigDecimal x1 (* x 2M)]
(if (== a b)
(let [#^BigDecimal absum1 (+ a1 b1)
#^BigDecimal absqrd (* absum1 absum1)
#^BigDecimal u (* t1 4M)]
(-> absqrd
(.divide u digits round-mode)
(.setScale places round-mode)))
(recur a1 b1 t1 x1 y1))))]
(sb-pi-int 1M (-> 1M (.divide #^BigDecimal (big-sqrt 2M) digits round-mode))
(/ 1M 4M) 1M nil))))
(time (println (sb-pi (Integer/parseInt (second *command-line-args*)))))
$ time clj pi.clj 1 --> 3.403 msecs
$ time clj pi.clj 10 --> 3.956 msecs
$ time clj pi.clj 100 --> 10.630 msecs
$ time clj pi.clj 1000 --> 141.937 msecs
$ time clj pi.clj 10000 --> 3316.180 msecs
The same algorithm in Java (but using iteration instead of recursion) :
import java.math.BigDecimal;
import static java.math.BigDecimal.*;
class Pi {
private static final BigDecimal TWO = new BigDecimal(2);
private static final BigDecimal FOUR = new BigDecimal(4);
private static int ROUND_MODE = ROUND_DOWN;
public static void main(String[] args) {
long start = System.nanoTime();
System.out.println(pi(Integer.parseInt(args[0])));
System.out.println("Elapsed time: " +
((System.nanoTime() - start) / 1E6) + " msecs");
}
// Salamin-Brent Algorithm
public static BigDecimal pi(final int digits) {
final int SCALE = 10 + digits;
BigDecimal a = ONE;
BigDecimal b = ONE.divide(sqrt(TWO, SCALE), SCALE, ROUND_MODE);
BigDecimal t = new BigDecimal(0.25);
BigDecimal x = ONE;
BigDecimal y;
while (!a.equals(b)) {
y = a;
a = a.add(b).divide(TWO, SCALE, ROUND_MODE);
b = sqrt(b.multiply(y), SCALE);
t = t.subtract(x.multiply(y.subtract(a).multiply(y.subtract(a))));
x = x.multiply(TWO);
}
return a.add(b)
.multiply(a.add(b))
.divide(t.multiply(FOUR), SCALE, ROUND_MODE)
.setScale(digits, ROUND_MODE);
}
// square root method (Newton's)
public static BigDecimal sqrt(BigDecimal A, final int SCALE) {
BigDecimal x0 = new BigDecimal("0");
BigDecimal x1 = new BigDecimal(Math.sqrt(A.doubleValue()));
while (!x0.equals(x1)) {
x0 = x1;
x1 = A.divide(x0, SCALE, ROUND_MODE);
x1 = x1.add(x0);
x1 = x1.divide(TWO, SCALE, ROUND_MODE);
}
return x1;
}
}
$ time java Pi 1 ----> 2.162 msecs
$ time java Pi 10 ----> 2.425 msecs
$ time java Pi 100 ----> 7.897 msecs
$ time java Pi 1000 ----> 150.610 msecs
$ time java Pi 10000 ----> 3009.705 msecs