Estimating Pi in JavaScript

Pi is an irrational number starting off 3.14159 and then carrying on for an infinite number of digits with no pattern which anybody has ever discovered. Therefore it cannot be calculated as such, just estimated to (in principle) any number of digits, and in this post I will implement a few of the many methods for doing so in JavaScript.

So far π has been calculated to 22,459,157,718,361 digits (nearly twenty two and a half trillion) which, by coincidence, is the same number of different ways there are of calculating it. OK, that's a slight exaggeration but there are many ways of getting the job done, ranging from the ancient 22/7 to recently discovered fast but complex methods such as the Chudnovsky Algorithm. These generally calculate all the digits up to a certain point but there are also a few so-called spigot algorithms which calculate a given digit without calculating all the preceding ones first.

There is plenty of information around regarding the number of digits of π which are actually necessary for practical purposes. This caught my eye though from NASA. You can calculate the circumference of the Universe to the accuracy of a hydrogen atom using just 40dp which is the number reached over three centuries ago! And you only need 62 digits to calculate the circumference to the Planck length, the smallest meaningful unit of length which you can think of as a quantum of space.

Trillions of digits aren't therefore useful in their own right but the effort put in to their calculation no doubt benefits mathematics and computer science.

In this project I will code a few of the simpler methods to give a decidedly non-rigorous introduction to what is actually a vast topic. If you want to do something rather more serious than playing with my code you can download the application called y-cruncher used to break the record here.


Starting to Code

This project consists of the following main files which you can download as a zip the code from the Downloads page or clone/download from Github if you prefer.

  • estimatingpi.htm
  • estimatingpi.js

Source Code Links

ZIP File
GitHub

There are also a few ancilliary files: a banner graphic, a CSS file and a JavaScript file containing a function which outputs text to the page.


Calling Functions and Printing Values

The following code snippet shows the first two functions in estimatingpi.js. The onload event handler simply calls the various functions which we'll implement and uncomment one by one. The printAsText function uses a string constant to hold a definitive value of Pi to 15dp. This is printed, along with the individual digits of the calculated value which are shown in green or red (using classes from the CSS file) depending on whether or not they are correct.

estimatingpi.js part 1

window.onload = function()
{
    writeToConsole("EstimatingPi<br/><br/>", "console");

    //Fractions();

    //FrancoisViete();

    //JohnWallis();

    //JohnMachin();

    //GregoryLeibniz();

    //Nilakantha();
}


function printAsText(pi)
{
    const PI_STRING = "3.141592653589793";

    let pi_string = pi.toFixed(15);

    writeToConsole(`Definitive ${PI_STRING}<br/>Calculated `, "console");

    for(let i = 0, l = pi_string.length; i < l; i++)
    {
        if(pi_string[i] == PI_STRING[i])
        {
            writeToConsole(`<span class="green">${pi_string[i]}</span>`, "console");
        }
        else
        {
            writeToConsole(`<span class="red">${pi_string[i]}</span>`, "console");
        }
    }

    writeToConsole("<br/><br/>", "console");
}


Estimating Pi Using Fractions

The first of the six methods we will use is the simplest and just consists of dividing one number by another, or to look at it another way converting fractions to decimals

estimatingpi.js part 2

function Fractions()
{
    let pi = 22 / 7;
    writeToConsole("22/7<br/>====<br/>", "console");
    printAsText(pi);

    pi = 333 / 106;
    writeToConsole("333/106<br/>=======<br/>", "console");
    printAsText(pi);

    pi = 355 / 113;
    writeToConsole("355/113<br/>=======<br/>", "console");
    printAsText(pi);

    pi = 52163 / 16604;
    writeToConsole("52163/16604<br/>===========<br/>", "console");
    printAsText(pi);

    pi = 103993 / 33102;
    writeToConsole("103993/33102<br/>============<br/>", "console");
    printAsText(pi);

    pi = 245850922 / 78256779;
    writeToConsole("245850922/78256779<br/>==================<br/>", "console");
    printAsText(pi);
}

The variable pi is set to each of six different fractions and then printed using the printAsText function. Uncomment Fractions in onload and open estimatingpi.htm in your browser.

As you can see the longer fractions give us ever increasing accuracy. The second fraction looks odd as it has a seemingly correct digit after a number of wrong ones but that it just a coincidence. It is quite impressive that pi can be calculated to such accuracy with a simple long division, although I suspect the later fractions were discovered after pi had already been calculated to that accuracy using more complex methods.


Francois Viete

The following formula was discovered by French mathematician Francois Viete in 1593.

These are just the first three terms of an infinite series and already grow to somewhat monstrous proportions. However, if you look closely you can see that each numerator (the expression on the top of the fraction) is just the square root of 2 plus the previous numerator. This even applies to the first term if you assume the previous numerator is 0, so let's use that insight to implement Monsieur Viete's infinite product.

estimatingpi.js part 3

function FrancoisViete()
{
    writeToConsole("Francois Viete<br/>==============<br/>", "console");

    let iterations = 28;
    let numerator = 0;
    let pi = 1;

    for(let i = 1; i <= iterations; i++)
    {
        numerator = Math.sqrt(2 + numerator);

        pi*= (numerator / 2);
    }

    pi = (1 / pi) * 2;

    printAsText(pi);
}

The iterations variable specifies how many terms we will calculate, and the numerator is initialized to 0 as mentioned above. The pi variable is initialised to 1 so we can multiply the value of the first term by it without needing any special case for the first term.

Then we enter a for loop, setting the numerator to the square root of 2 + itself as described above, after which pi is multiplied by the evaluated term. Again we do not need a special case for the first term as pi is initialised to 1.

You probably noticed that the formula does not give us π but 2/π so after the loop we need to calculate pi itself and than call print_as_text. Uncomment FrancoisViete in onload and then refresh the page.

Just 28 terms (I guess calculable by hand four hundred years ago with care and patience) give us 14dp of accuracy. Not bad!


John Wallis

English mathematician John Wallis came up with this in 1655, another infinite product (ie. each term is multiplied by the previous), this one giving us π/2 rather than 2/π.

It's easy to see what is going on here, the denominator and numerator are alternately incremented by 2. In the code, if the term is odd we'll increment the denominator, and if it's even we'll increment the numerator.

estimatingpi.js part 4

function JohnWallis()
{
    writeToConsole("John Wallis<br/>===========<br/>", "console");

    let iterations = 1000000;
    let numerator = 2;
    let denominator = 1;
    let pi = 1;

    for(let i = 1; i <= iterations; i++)
    {
        pi*= (numerator / denominator);

        ((i%2) == 1) ? (denominator+= 2) : (numerator+= 2);
    }

    pi*= 2;

    printAsText(pi);
}

The number of iterations is set to 1,000,000 (yes, really) and the numerator and denominator are set to 2 and 1 respectively, as per the first term. The pi variable is again set to 1 so we can multiply the first term by it without a special case.

The for loop is simple, it just multiplies pi by the next term, incrementing the numerator or denominator depending on whether the current term is odd or even.

So far we only have half of pi so after the loop it's multiplied by 2 and printed. Uncomment JohnWallis in onload and then refresh the page.

A million terms, waaaaaay beyond what Mr Wallis could have calculated, and we only get 5dp of accuracy. I described Francois Viete's effort as "not bad" but I have to describe John Wallis's as "not good".


John Machin

This is English astronomer John Machin's contribution from 1706, which he used to calculate 100 digits. Others used his formula to calculate many more digits by hand and it was the most used until the 20th century.

It looks simple - no infinite series, just one calculation. However, the complexity is hidden in the use of arctan although we have JavaScript's Math library whereas Professor Machin did not.

estimatingpi.js part 5

function JohnMachin()
{
    writeToConsole("John Machin<br/>===========<br/>", "console");

    let pi = (4 * Math.atan(1/5) - Math.atan(1/239)) * 4;

    printAsText(pi);
}

This function is very straightforward, just a translation of the formula into JavaScript. The formula only gives us a measly quarter of π so we need to multiply it by 4 at the end.

Uncomment JohnMachin in onload and then refresh the page.

This looks impressive but of course the accuracy is down to the arctangent we have available. I am not sure how this would have been calculated in Machin's time or how much accuracy he would have achieved.


Gregory-Leibniz

Next we have the Gregory-Leibniz series. This is very simple, it gives us π rather than a fraction or multiple of it and the numerator is constant. All we need to do is add 2 to the denominator each time and flip between subtraction and addition.

estimatingpi.js part 6

function GregoryLeibniz()
{
    writeToConsole("Gregory-Leibniz<br/>===============<br/>", "console");

    let iterations = 400000;
    let denominator = 1;
    let multiplier = 1;
    let pi = (4 / denominator);

    for(let i = 2; i <= iterations; i++)
    {
        pi += ( (4 / (denominator += 2)) * (multiplier *= -1) );
    }

    printAsText(pi);
}

The denominator variable is first set to 1, and we also have a multiplier which will alternate between 1 and -1 to allow for the alternating addition and subtraction. Pi is initialised to the first term.

The for loop starts at the second term, taking the present value of pi and adding the next term. Within the evaluation of the term the denominator is incremented, and the value multiplied by 1 or -1 to allow for the alternating addition and subtraction. The multiplier is also "flipped" within the expression.

Uncomment the function in onload and refresh the page.

I have to admit the result is a bit baffling. The second batch of accurate digits cannot be chance as with 333/106, but why the wildly inaccurate two digits before them?


Nilakantha

Finally another infinite sum, this one from the 15th century Indian mathematician Nilakantha Somayaji, which has some obvious similarities with the Gregory-Leibniz series.

So we start off with 3, have a constant numerator of 4, and calculate the denominator by multiplying three numbers. We also alternate between addition and subtraction.

estimatingpi.js part 7

function Nilakantha()
{
    writeToConsole("Nilakantha<br/>==========<br/>", "console");

    let iterations = 1000000;
    let multiplier = -1;
    let start_denominator = 2;
    let pi = 3;

    for(let i = 1; i <= iterations; i++)
    {
        pi += ( (4 / (start_denominator * (start_denominator + 1) * (start_denominator + 2)) ) * (multiplier *= -1));

        start_denominator += 2;
    }

    printAsText(pi);
}

Again we have a multiplier to alternate between addition and subtraction. The start_denominator variable represents the first of the three numbers which are multiplied to get the denominator, and will be incremented by 2 on each iteration. We then start of pi at 3.

Within the loop the numerator 4 is divided by the denominator which is calculated by multiplying start_denominator by its two consective numbers, this then being multiplied by 1 or -1 alternately. Lastly within the loop 2 is added to start_denominator.

Uncomment nilakantha in onload and then refresh the page.

This is pretty good, but even 50 iterations gets us 5dp of accuracy. Remember that each additional digit only gives us at most a 10% increase in accuracy.