# Meet π

Calculating Pi to high precision in a web browser

Written by Guy Fernando

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.

### Brief History of π

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.

### Online π Calculator

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.

##### Equation for the Madhava algorithm

\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);



### Newton-Euler Algorithm

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 π.

##### Equation for the Newton-Euler algorithm

\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);



### Ramanujan Algorithm

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 π.

##### Equation for the Ramanujan algorithm

\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);



### Chudnovsky Algorithm

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.

##### Equation for the Chudnovsky algorithm

\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);



### Bailey Borwein Plouffe Algorithm

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.

##### Equation for the Bailey Borwein Plouffe (BBP) algorithm

\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);



### Big 'O' Notation

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.

### Computation Method Used Here

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.
//
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 0.4);
}

//
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;
}
}
}

//
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;

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));
};




### Algorithm Benchmark Results

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

### Conclusion

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.