Calculating Pi to high precision in a web browser

Written by Guy Fernando

Created Dec 2019 - Last modified Nov 2024

Pi or π is defined as being the ratio of a circle’s circumference to its diameter. This ratio when expressed in any
numerical base (radix) is an irrational number, meaning the sequence of numbers generated does not ever repeat or terminate.
It is also a transcendental number, meaning that it is not the root of a non-zero polynomial with finite degree with rational
coefficients.

It is worth remembering, that any two numbers expressed as a fraction will eventually repeat or terminate.
Therefore π can never be truly represented by a fraction.

Approximations for the value π were known to the ancient Babylonians, Egyptians, Indians, Greeks, and Chinese. The
earliest records of π date back to the Babylonians (c. 2000 BCE), they referred to π as the fraction 25/8 = 3.125,
out by 0.528%. Roughly around the same time, the Egyptians were using a different fraction 256/81 = 3.160, out by 0.601%.
Indian scriptures contained in the Vedic Shatapatha Brahmana book (c. 800 BCE) referred to π as 339/108 = 3.138̇̇̇̇̇̇̇̇, out
by 0.086%. Approximately five hundred years later (c. 250B CE), Archimedes of Syracuse used inscribed and circumscribed
polygons, and calculated π to be 22/7, out by 0.040%. This easy to remember approximation was still recently in use
for performing rough calculations until the introduction of the modern electronic calculator. Zu Chongzhi a Chinese polymath
(c. 500 CE) some seven hundred years later derived two approximations. The first being the same as the one Archimedes found,
and later the more impressive approximation of 355/113 = 3.14159292, being accurate to seven decimal places, an incredible
feat for the time.

The beginning of the European renaissance (c. 1400 CE), and the introduction of the Hindu-Arabic numeral system by
Fibonacci at around the same time, paved the way for major advances in mathematics in Europe. By the late sixteenth century,
European mathematicians were using infinite series equations to calculate the value of π with greater and greater precision.
Since then, a variety of ingenious methods for approximating π, including the use of prime numbers, and even the use of the
golden ratio have come to pass.

The online π calculator presented here is able to evaluate π up to one million decimal places using one of the infinite
series algorithms. The more digits selected the longer the calculation will take to complete, and some algorithms are faster
than others. Be prepared to wait around sometime when calculating π to more than 1000 digits, though this will depend on
the speed of your computer, and the algorithm chosen. When the calculation completes, the result will be displayed below.

Select the required algorithm and number of digits to calculate, and then click the Calculate button.

The process of evaluating π when using an infinite series involves a repetition of calculations. The more repetitions
we use the better the approximation. Whether performing these calculations by hand or using a digital computer, it is obviously
advantageous to arrive upon an answer with the fewest of repetitions. Some infinite series equations converge faster
than others, which is another way of saying that the value is arrived upon more quickly for a given amount of repetitions
or iterations.

Madhava of Sangamagrama (c. 1340 – c. 1425) was an Indian mathematician, and was the first to use an infinite series to
calculate π. Gottfried Wilhelm Leibniz, a German mathematician independently published the same series more than two
hundred years later in 1676. As a consequence it is now known in the west as the
Madhava-Leibniz series. Madhava also
devised an improved series that converges more rapidly than the original series.

\( \begin{aligned}
\pi = \sqrt{12} \sum_{k=1}^ \infty \frac{ {(-1)}^{k+1} }{(2k - 1). 3^{k-1} }
\end{aligned} \)

Digits calculated per iteration (convergence): ≅ 0.4

Computational complexity:
\( \begin{aligned}
O( k^2 )
\end{aligned} \)

The JavaScript implementation of the Madhava algorithm is shown here.

` ````
// Madhava algorithm for calculating Pi.
// Guy Fernando (2019)
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 2;
for (var k = 1; k < iterations; k++) {
// Numerator term, Nk = (-1)^(k+1)
Nk = Decimal(-1).pow(k + 1);
// Denominator term, Dk = (2k - 1) * 3^(k-1)
Dk = Decimal((2 * k) - 1).times(Decimal(3).pow(k - 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 12^0.5.
pi = pi.times(Decimal(12).sqrt());
// Set significant digits.
pi = pi.toSD(this.digits);
```

Isaac Newton (c. 1643 - c. 1727) was an English polymath who is widely recognised as one of the most influential
scientists of all time. In 1666, Newton used a geometric construction to derive an infinite series for π. Leonhard
Euler (c. 1707 - c. 1783) a Swiss polymath known to be one of the most eminent mathematicians of the 18th century
improved Newton's original infinite series for π.

\( \begin{aligned}
\frac{ \pi }{2} = \sum_{k=0}^ \infty \frac{2^{k} (k!)^{2} }{(2k + 1)!}
\end{aligned} \)

Digits calculated per iteration (convergence): ≅ 0.3

Computational complexity:
\( \begin{aligned}
O( k.log^2(k))
\end{aligned} \)

The JavaScript implementation of the Newton-Euler algorithm is shown here.

` ````
// Newton-Euler algorithm for calculating Pi.
// Guy Fernando (2019)
Decimal.precision = this.digits + 3;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Numerator term, Nk = 2^k * (k!)^2
Nk = Decimal(2).pow(k).times(Decimal(this.factorial(k)).pow(2));
// Denominator term, Dk = (2k + 1)!
Dk = Decimal(this.factorial((2 * k) + 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 2.
pi = pi.times(2);
// Set significant digits.
pi = pi.toSD(this.digits);
```

Srinivasa Ramanujan (c. 1887 - c. 1920) was an Indian mathematician. Despite having no formal education and dying
young he made substantial contributions to mathematical analysis, number theory, infinite series, and continued
fractions, including solutions to mathematical problems at the time considered unsolvable.
Ramanujan's series for
π converges extraordinarily rapidly and forms the basis of some of the fastest algorithms currently used to
calculate π.

\( \begin{aligned}
\frac{1}{ \pi } = \frac{2 \sqrt{2} }{9801} \sum_{k=0}^ \infty \frac{(4k)!(1103 + 26390k)}{ (k!)^{4} 396^{4k}}
\end{aligned} \)

Digits calculated per iteration (convergence): ≅ 8

Computational complexity:
\( \begin{aligned}
O( k.log^3(k))
\end{aligned} \)

The JavaScript implementation of the Ramanujan algorithm is shown here.

` ````
// Ramanujan algorithm for calculating Pi.
// Guy Fernando (2019)
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (4k)! / (k!)^4
Mk = Decimal(this.factorial(4 * k)).div(Decimal(this.factorial(k)).pow(4));
// Linear term, Lk = 1103 + 26390k
Lk = Decimal(26390 * k).plus(1103);
// Exponential term, Xk = 396^4k
Xk = Decimal(396).pow(4 * k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = (2 * 2^0.5) / 9801
C = Decimal(2).times(Decimal(2).sqrt()).div(9801);
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
```

David Volfovich Chudnovsky (c. 1947) and Gregory Volfovich Chudnovsky (c. 1952) are American mathematicians and
engineers known for their world-record mathematical calculations and developing the
Chudnovsky algorithm used to
calculate the digits of π with extreme precision. The Chudnovsky algorithm published in 1988, is based on the
Ramanujan algorithm, but converges at about twice the rate. It is the Chudnovsky algorithm that has been used to
calculate the world record for π to 31.4 trillion digits.

\( \begin{aligned}
\frac{426880 \sqrt{10005} }{ \pi } = \sum_{k=0}^ \infty \frac{(6k)!(545140134k + 13591409)}{(3k)! (k!)^{3} (-262537412640768000)^{k}}
\end{aligned} \)

Digits calculated per iteration (convergence): ≅ 14

Computational complexity:
\( \begin{aligned}
O( k.log^3(k))
\end{aligned} \)

The JavaScript implementation of the Chudnovsky algorithm is shown here.

` ````
// Chudnovsky algorithm for calculating Pi.
// Guy Fernando (2019)
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (6k)! / (3k)! * (6k)!^3
Mk = Decimal(this.factorial(6 * k)).div(Decimal(this.factorial(3 * k)).times(Decimal(this.factorial(k)).pow(3)));
// Linear term, Lk = 545140134k + 13591409
Lk = Decimal(545140134 * k).plus(13591409);
// Exponential term, Xk = -262537412640768000^k
Xk = Decimal(-262537412640768000).pow(k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = 1 / (426880 * 10005^0.5)
C = Decimal(1).div(Decimal(426880).times(Decimal(10005).sqrt()));
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
```

The Bailey Borwein Plouffe (BBP) algorithm was discovered by Simon Plouffe in 1995, and is named after the three authors that wrote the
original BBP algorithm paper,
David H. Bailey, Peter Borwein, and Simon Plouffe.
This algorithm may also be arranged to produce a spigot algorithm
for calculating any nth digit of π. However here we calculate the complete value of π to a required number of digits.

\( \begin{aligned}
\pi = \sum_{k=0}^ \infty \big[ \frac{1}{ 16^{k} } \big( \frac{4}{8k + 1} - \frac{2}{8k + 4} - \frac{1}{8k + 5} - \frac{1}{8k + 6} \big) \big]
\end{aligned} \)

Digits calculated per iteration: = 1

Computational complexity:
\( \begin{aligned}
O( k.log(k))
\end{aligned} \)

The JavaScript implementation of the Bailey Borwein Plouffe (BBP) algorithm is shown here.

` ````
// Bailey Borwein Plouffe (BBP) algorithm for calculating Pi.
// Guy Fernando (2023)
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var M, T1, T2, T3, T4;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multiplier, M = 1 / 16^k
M = Decimal(1).div(Decimal(16).pow(Decimal(k)));
// Term 1, T1 = 4 / (8k + 1)
T1 = Decimal(4).div(Decimal(8).times(Decimal(k)).plus(1));
// Term 2, T2 = 2 / (8k + 4)
T2 = Decimal(2).div(Decimal(8).times(Decimal(k)).plus(4));
// Term 3, T3 = 1 / (8k + 5)
T3 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(5));
// Term 4, T4 = 1 / (8k + 6)
T4 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(6));
// Pi partial summation.
pi = pi.plus(M.times(T1.minus(T2).minus(T3).minus(T4)));
}
// Set significant digits.
pi = pi.toSD(this.digits);
```

In computer science, the Big 'O' notation
is used as a basis to quantify computational complexity. The notation applies to algorithms that are repetitive such
as in a software loop construct, k being the number iterations before the loop terminates. The notation is a technique
used to classify an algorithm's running time for a given number of iterations. Since the algorithms presented here all
share the same structure, they too share the same computational complexity classification.

The computation is performed within your web browser using JavaScript. The calculation is executed in a separate web
worker thread to allow this web page to remain responsive and to be scrolled while the calculation is in progress.

To evaluate an approximation of π to high precision, the built-in 64-bit IEEE 754 floating point numerical system that
JavaScript uses cannot be used beyond 15 decimal places. To achieve higher accuracy, an
arbitrary precision numeric
library such as Decimal.js is used. Decimal.js is a self
contained open-source library with a large community of users. It is used to calculate all the algorithms presented in this
article to a precision of up to a million decimal places.

The full javascript code listing is shown here.

` ````
// Guy Fernando (2023).
// Calculates PI using various algorithms.
// Implementation using arbitary precision arithmetic library, decimal.js.
// When calculating pi, decimal.js is faster than big.js.
// For algorithms see: http://mathworld.wolfram.com/PiFormulas.html
importScripts('decimal.min.js');
// Abstract base class for calculating pi.
//
class PiAlgorithm {
// Constructor.
//
constructor(digits, digitsPerIteration) {
this.digits = digits;
this.digitsPerIteration = digitsPerIteration;
this.startTime = 0;
this.endTime = 0;
}
// Factorial that doesn't overflow with large n.
//
factorial(n) {
try {
var i = 2, r = new Decimal(1);
for (; i <= n; r = r.times(i++))
;
}
catch (err) {
console.log(err.message);
}
return r;
}
// Gets the time taken in milliseconds to calculate a single pi digit.
//
getTimePerDigit() {
return ((this.endTime - this.startTime) / this.digits).toFixed(2);
}
}
// Bailey Borwein Plouffe (BBP) method for calculating pi.
//
class BBP extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 1.0);
}
// The Bailey Borwein Plouffe (BBP) pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var M, T1, T2, T3, T4;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multiplier, M = 1 / 16^k
M = Decimal(1).div(Decimal(16).pow(Decimal(k)));
// Term 1, T1 = 4 / (8k + 1)
T1 = Decimal(4).div(Decimal(8).times(Decimal(k)).plus(1));
// Term 2, T2 = 2 / (8k + 4)
T2 = Decimal(2).div(Decimal(8).times(Decimal(k)).plus(4));
// Term 3, T3 = 1 / (8k + 5)
T3 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(5));
// Term 4, T4 = 1 / (8k + 6)
T4 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(6));
// Pi partial summation.
pi = pi.plus(M.times(T1.minus(T2).minus(T3).minus(T4)));
}
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Chudnovsky method for calculating pi.
//
class Chudnovsky extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 14.1816474627254776555);
}
// The Chudnovsky pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (6k)! / (3k)! * (6k)!^3
Mk = Decimal(this.factorial(6 * k)).div(Decimal(this.factorial(3 * k)).times(Decimal(this.factorial(k)).pow(3)));
// Linear term, Lk = 545140134k + 13591409
Lk = Decimal(545140134 * k).plus(13591409);
// Exponential term, Xk = -262537412640768000^k
Xk = Decimal(-262537412640768000).pow(k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = 1 / (426880 * 10005^0.5)
C = Decimal(1).div(Decimal(426880).times(Decimal(10005).sqrt()));
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Ramanujan method for calculating pi.
//
class Ramanujan extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 8.0);
}
// The Ramanujan pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (4k)! / (k!)^4
Mk = Decimal(this.factorial(4 * k)).div(Decimal(this.factorial(k)).pow(4));
// Linear term, Lk = 1103 + 26390k
Lk = Decimal(26390 * k).plus(1103);
// Exponential term, Xk = 396^4k
Xk = Decimal(396).pow(4 * k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = (2 * 2^0.5) / 9801
C = Decimal(2).times(Decimal(2).sqrt()).div(9801);
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Newton-Euler method for calculating pi.
//
class NewtonEuler extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 0.3);
}
// The Newton-Euler pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 3;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Numerator term, Nk = 2^k * (k!)^2
Nk = Decimal(2).pow(k).times(Decimal(this.factorial(k)).pow(2));
// Denominator term, Dk = (2k + 1)!
Dk = Decimal(this.factorial((2 * k) + 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 2.
pi = pi.times(2);
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Madhava method for calculating pi.
//
class Madhava extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 0.4);
}
// The Madhava pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 2;
for (var k = 1; k < iterations; k++) {
// Numerator term, Nk = (-1)^(k+1)
Nk = Decimal(-1).pow(k + 1);
// Denominator term, Dk = (2k - 1) * 3^(k-1)
Dk = Decimal((2 * k) - 1).times(Decimal(3).pow(k - 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 12^0.5.
pi = pi.times(Decimal(12).sqrt());
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Web worker message receiver.
//
self.onmessage = function (event) {
// Reconstitute json object containing parameters.
var jsonParameters = JSON.parse(event.data);
// Extract parameters.
var digits = Number(jsonParameters.Digits);
var algorithm = jsonParameters.Algorithm;
// Instantiate required algorithm.
var pi;
switch (algorithm) {
case "BBP":
pi = new BBP(digits);
break;
case "Chudnovsky":
pi = new Chudnovsky(digits);
break;
case "Ramanujan":
pi = new Ramanujan(digits);
break;
case "Newton-Euler":
pi = new NewtonEuler(digits);
break;
case "Madhava":
pi = new Madhava(digits);
break;
}
// Perform pi calculation.
const piValue = pi.calculate();
const timePerDigit = pi.getTimePerDigit();
// Send json message containing results to main thread.
var jsonResult = { Algorithm: algorithm, Digits: digits, PiValue: piValue.toString(), TimePerDigit: timePerDigit.toString() };
self.postMessage(JSON.stringify(jsonResult));
};
```

The following table shows the per digit time taken (in milliseconds) when calculating π to a differing number of digits using a web browser.

Algorithm | 500 Digits | 1000 Digits | 5000 Digits |
---|---|---|---|

Chudnovsky |
0.02 ms | 0.08 ms | 1.71 ms |

Ramanujan |
0.04 ms | 0.14 ms | 3.96 ms |

BBP |
0.25 ms | 0.84 ms | 21.13 ms |

Madhava |
0.38 ms | 1.22 ms | 32.51 ms |

Newton-Euler |
16.51 ms | 63.63 ms | 2017.41ms |

All the algorithms presented in this article are infinite series, as a result computational memory requirements and execution
time will be a function of the number of digits that need to be calculated. Furthermore the computer memory requirements
will roughly be proportional to the number of digits required, and the computer execution time per digit will exponentially
increase as the number of digits required increases. Decimal.js is an excellent small footprint arbitrary-precision library
for JavaScript. It lends itself perfectly for evaluating infinite series algorithms as described in this article.

Incredibly, the Madhava algorithm despite being devised three centuries earlier than the Newton-Euler algorithm actually
converges around 50 times faster. The modern day algorithms, which you would expect,
converge even faster. The Ramanujan and Chudnovsky algorithms are both similar having multinomial, linear and exponential
terms. Although these algorithms converge faster, they are computationally more complex and as a result will take an
increasingly longer time to calculate subsequent digits. They are around 400 and 700 times faster respectively than the
Newton-Euler algorithm.

Unquestionably this is not the end of the story, and surely in time new algorithms will be conceived that converge even
quicker than the Chudnovsky algorithm.

This website is powered using ultra low power green locally based servers.

Copyright © i4cy 2000-2024. All rights reserved.