How to calculate PI with "n" decimal places in Javascript?

Asked

Viewed 1,826 times

19

Using the object Math of Javascript, I can return an PI value with fixed decimal places, example:

Math.PI //3.141592653589793

But in case I need (yes, very unusual) calculate the same with more decimal places, is there any other way to define a number of decimal places? Example:

Math.pi(300) //retornaria com precisão de 300 casas decimais, etc.

3 answers

28


The first thing you need is a library of arbitrary precision decimal number manipulation. A double (numeric format used by Javascript for type Number) is accurate to a maximum of 15 decimal places (floor(log10(2^53))), so that if you need to do any account involving more houses than that, you need to represent those numbers in some other way.

(and you whether do π accounts, right? Because if it was just a matter of displaying it, a pre-populated string with its value would suffice...)

Once you’ve chosen a library, you have two options:

  1. For practical use, it is preferable get the value of π from somewhere instead of calculating it. A 300 byte string does not carry a overhead so great for your system... (especially for the more code you would have to put in to do your calculation)
  2. If it is really necessary (or desirable, for example for study purposes) to calculate yourself, there are several formulas that converge to π. One of them (the first suggestion from maniero’s response) is π = 48*arctan(1/18) + 32*arctan(1/57) − 20*arctan(1/239), where the tangent arc can be calculated by means of an infinite series. Other infinite series and/or expressions also converge to π.

I can not say which is the best (nor what is the best criterion to define "the best"), so I will give an example using the arc sine, because it seems to me very easy to calculate:

                1 * x^3   1 * 3 * x^5   1 * 3 * 5 * x^7
arcsin(x) = x + ------- + ----------- + --------------- + ...
                2 * 3     2 * 4 * 5     2 * 4 * 6 * 7

arcsin(1/2) = π/6

Using the library javascript-bignum:

var pi = new BigNumber("3", precisao + 5); // Começa com 3 (6*x, x == 1/2)
var antigo = new BigNumber("0", precisao + 5);

var numerador = new BigNumber("6", precisao + 5); // O numerador da próxima parcela
var denominador = new BigNumber("1", precisao + 5); // Parte do denominador (comum)
var potencia2 = new BigNumber("2", precisao + 5); // Outra parte (variável)

var indice = 1; // Qual parcela está sendo acrescentada

function passo() {
  if ( pi.compare(antigo) == 0 ) // Se o valor não mudou, já convergiu
    return;

  antigo = pi; // Salva o valor antigo

  numerador = numerador.multiply(indice);
  denominador = denominador.multiply(indice+1);
  potencia2 = potencia2.multiply(4);

  // Acrescenta a próxima parcela na série
  var parcela = numerador.divide(denominador).divide(indice+2).divide(potencia2);
  pi = pi.add(parcela);

  indice += 2
  setTimeout(passo, 0); // Executa o próximo passo (assíncrono)
}
passo(); // Começa

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/classes/bignumber [rev. #4]

BigNumber = function(n, p, r){
    var o = this, i;
    if(n instanceof BigNumber){
        for(i in {precision: 0, roundType: 0, _s: 0, _f: 0}) o[i] = n[i];
        o._d = n._d.slice();
        return;
    }
    o.precision = isNaN(p = Math.abs(p)) ? BigNumber.defaultPrecision : p;
    o.roundType = isNaN(r = Math.abs(r)) ? BigNumber.defaultRoundType : r;
    o._s = (n += "").charAt(0) == "-";
    o._f = ((n = n.replace(/[^\d.]/g, "").split(".", 2))[0] = n[0].replace(/^0+/, "") || "0").length;
    for(i = (n = o._d = (n.join("") || "0").split("")).length; i; n[--i] = +n[i]);
    o.round();
};
with({$: BigNumber, o: BigNumber.prototype}){
    $.ROUND_HALF_EVEN = ($.ROUND_HALF_DOWN = ($.ROUND_HALF_UP = ($.ROUND_FLOOR = ($.ROUND_CEIL = ($.ROUND_DOWN = ($.ROUND_UP = 0) + 1) + 1) + 1) + 1) + 1) + 1;
    $.defaultPrecision = 40;
    $.defaultRoundType = $.ROUND_HALF_UP;
    o.add = function(n){
        if(this._s != (n = new BigNumber(n))._s)
            return n._s ^= 1, this.subtract(n);
        var o = new BigNumber(this), a = o._d, b = n._d, la = o._f,
        lb = n._f, n = Math.max(la, lb), i, r;
        la != lb && ((lb = la - lb) > 0 ? o._zeroes(b, lb, 1) : o._zeroes(a, -lb, 1));
        i = (la = a.length) == (lb = b.length) ? a.length : ((lb = la - lb) > 0 ? o._zeroes(b, lb) : o._zeroes(a, -lb)).length;
        for(r = 0; i; r = (a[--i] = a[i] + b[i] + r) / 10 >>> 0, a[i] %= 10);
        return r && ++n && a.unshift(r), o._f = n, o.round();
    };
    o.subtract = function(n){
        if(this._s != (n = new BigNumber(n))._s)
            return n._s ^= 1, this.add(n);
        var o = new BigNumber(this), c = o.abs().compare(n.abs()) + 1, a = c ? o : n, b = c ? n : o, la = a._f, lb = b._f, d = la, i, j;
        a = a._d, b = b._d, la != lb && ((lb = la - lb) > 0 ? o._zeroes(b, lb, 1) : o._zeroes(a, -lb, 1));
        for(i = (la = a.length) == (lb = b.length) ? a.length : ((lb = la - lb) > 0 ? o._zeroes(b, lb) : o._zeroes(a, -lb)).length; i;){
            if(a[--i] < b[i]){
                for(j = i; j && !a[--j]; a[j] = 9);
                --a[j], a[i] += 10;
            }
            b[i] = a[i] - b[i];
        }
        return c || (o._s ^= 1), o._f = d, o._d = b, o.round();
    };
    o.multiply = function(n){
        var o = new BigNumber(this), r = o._d.length >= (n = new BigNumber(n))._d.length, a = (r ? o : n)._d,
        b = (r ? n : o)._d, la = a.length, lb = b.length, x = new BigNumber, i, j, s;
        for(i = lb; i; r && s.unshift(r), x.set(x.add(new BigNumber(s.join("")))))
            for(s = (new Array(lb - --i)).join("0").split(""), r = 0, j = la; j; r += a[--j] * b[i], s.unshift(r % 10), r = (r / 10) >>> 0);
        return o._s = o._s != n._s, o._f = ((r = la + lb - o._f - n._f) >= (j = (o._d = x._d).length) ? this._zeroes(o._d, r - j + 1, 1).length : j) - r, o.round();
    };
    o.divide = function(n){
        if((n = new BigNumber(n)) == "0")
            throw new Error("Division by 0");
        else if(this == "0")
            return new BigNumber;
        var o = new BigNumber(this), a = o._d, b = n._d, la = a.length - o._f,
        lb = b.length - n._f, r = new BigNumber, i = 0, j, s, l, f = 1, c = 0, e = 0;
        r._s = o._s != n._s, r.precision = Math.max(o.precision, n.precision),
        r._f = +r._d.pop(), la != lb && o._zeroes(la > lb ? b : a, Math.abs(la - lb));
        n._f = b.length, b = n, b._s = false, b = b.round();
        for(n = new BigNumber; a[0] == "0"; a.shift());
        out:
        do{
            for(l = c = 0, n == "0" && (n._d = [], n._f = 0); i < a.length && n.compare(b) == -1; ++i){
                (l = i + 1 == a.length, (!f && ++c > 1 || (e = l && n == "0" && a[i] == "0")))
                && (r._f == r._d.length && ++r._f, r._d.push(0));
                (a[i] == "0" && n == "0") || (n._d.push(a[i]), ++n._f);
                if(e)
                    break out;
                if((l && n.compare(b) == -1 && (r._f == r._d.length && ++r._f, 1)) || (l = 0))
                    while(r._d.push(0), n._d.push(0), ++n._f, n.compare(b) == -1);
            }
            if(f = 0, n.compare(b) == -1 && !(l = 0))
                while(l ? r._d.push(0) : l = 1, n._d.push(0), ++n._f, n.compare(b) == -1);
            for(s = new BigNumber, j = 0; n.compare(y = s.add(b)) + 1 && ++j; s.set(y));
            n.set(n.subtract(s)), !l && r._f == r._d.length && ++r._f, r._d.push(j);
        }
        while((i < a.length || n != "0") && (r._d.length - r._f) <= r.precision);
        return r.round();
    };
    o.mod = function(n){
        return this.subtract(this.divide(n).intPart().multiply(n));
    };
    o.pow = function(n){
        var o = new BigNumber(this), i;
        if((n = (new BigNumber(n)).intPart()) == 0) return o.set(1);
        for(i = Math.abs(n); --i; o.set(o.multiply(this)));
        return n < 0 ? o.set((new BigNumber(1)).divide(o)) : o;
    };
    o.set = function(n){
        return this.constructor(n), this;
    };
    o.compare = function(n){
        var a = this, la = this._f, b = new BigNumber(n), lb = b._f, r = [-1, 1], i, l;
        if(a._s != b._s)
            return a._s ? -1 : 1;
        if(la != lb)
            return r[(la > lb) ^ a._s];
        for(la = (a = a._d).length, lb = (b = b._d).length, i = -1, l = Math.min(la, lb); ++i < l;)
            if(a[i] != b[i])
                return r[(a[i] > b[i]) ^ a._s];
        return la != lb ? r[(la > lb) ^ a._s] : 0;
    };
    o.negate = function(){
        var n = new BigNumber(this); return n._s ^= 1, n;
    };
    o.abs = function(){
        var n = new BigNumber(this); return n._s = 0, n;
    };
    o.intPart = function(){
        return new BigNumber((this._s ? "-" : "") + (this._d.slice(0, this._f).join("") || "0"));
    };
    o.valueOf = o.toString = function(){
        var o = this;
        return (o._s ? "-" : "") + (o._d.slice(0, o._f).join("") || "0") + (o._f != o._d.length ? "." + o._d.slice(o._f).join("") : "");
    };
    o._zeroes = function(n, l, t){
        var s = ["push", "unshift"][t || 0];
        for(++l; --l;  n[s](0));
        return n;
    };
    o.round = function(){
        if("_rounding" in this) return this;
        var $ = BigNumber, r = this.roundType, b = this._d, d, p, n, x;
        for(this._rounding = true; this._f > 1 && !b[0]; --this._f, b.shift());
        for(d = this._f, p = this.precision + d, n = b[p]; b.length > d && !b[b.length -1]; b.pop());
        x = (this._s ? "-" : "") + (p - d ? "0." + this._zeroes([], p - d - 1).join("") : "") + 1;
        if(b.length > p){
            n && (r == $.DOWN ? false : r == $.UP ? true : r == $.CEIL ? !this._s
            : r == $.FLOOR ? this._s : r == $.HALF_UP ? n >= 5 : r == $.HALF_DOWN ? n > 5
            : r == $.HALF_EVEN ? n >= 5 && b[p - 1] & 1 : false) && this.add(x);
            b.splice(p, b.length - p);
        }
        return delete this._rounding, this;
    };
}

var continuar = true;
document.getElementById("calcular").onclick = function() {
    var precisao = parseInt(document.getElementById("precisao").value, 10);

    var pi = new BigNumber("3", precisao + 5);
    var antigo = new BigNumber("0", precisao + 5);

    var numerador = new BigNumber("6", precisao + 5);
    var denominador = new BigNumber("1", precisao + 5);
    var potencia2 = new BigNumber("2", precisao + 5);
  
    var indice = 1;
    function passo() {
      if ( pi.compare(antigo) == 0 || !continuar )
        return;
      
      antigo = pi;
      
      numerador = numerador.multiply(indice);
      denominador = denominador.multiply(indice+1);
      potencia2 = potencia2.multiply(4);
      
      var parcela = numerador.divide(denominador).divide(indice+2).divide(potencia2);
      pi = pi.add(parcela);
      
      indice += 2
      atualizar();
      
      setTimeout(passo, 0);
    }
  
    continuar = true;
    passo();

  function atualizar() {
    var str1 = "" + pi;
    var str2 = "" + antigo;
    var saida = "<strong>";
    var igual = true;
    for ( var i = 0 ; i < precisao ; i++ ) {
      if ( igual && str1[i] != str2[i] ) {
        igual = false;
        saida += "</strong>"
      }
      saida += str1[i];
    }
    document.getElementById("saida").innerHTML = saida;
  }    
};

document.getElementById("parar").onclick = function() {
    continuar = false;
};
Precisão: <input id="precisao" type="number" value="70"/>
<button id="calcular">Calcular</button>
<button id="parar">Parar</button>
<p id="saida"></p>

In the above code example, precisao + 5 is to give a reasonable margin for convergence to take place (otherwise the smaller plots will be rounded to zero and will not enter the series). I put 70 decimal places, because it takes a long time to converge in the case of 300 (if you have the patience to wait, it should work, however).

  • Great answer! I was unaware of this possibility, and the excerpt of calculating code is perfectly self-explanatory, amazing.

6

Surely you would have to create an algorithm of your own and it’s not so simple.

I found that code. As there is no explicit license I do not know if I can bring here.

Another code which may be used as a reference.

If I find anything else that can improve the answer I will post here.

0

Pi One-Iner

Below some one-Liner using various languages... (almost all stolen! from various sources)

bash+bc (bc=Unix calculator)(tang(π/4)=1 therefore 4 x arcotang(1)= 4 x π/4= π)

bc -l <<< "scale=1000; 4*a(1)"

internet-based...

curl --silent http://www.angio.net/pi/digits/pi1000000.txt | cut -c1-3000

perl

perl -Mbignum=bpi -le 'print bpi(100)'

python

python -c "from mpmath import mp;mp.dps=500;print mp.pi"

for sure some include will have the PI with some houses...

grep -ohP '3.14\d{30,}' /usr/include/*.h

or even choose the most complete includes PI

grep -rohP '3\.14\d*' /usr/include | awk 'length > length(pi){pi=$0}  END{print pi}'
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651

If you have the Latex installed... (should be the latest version :)

tex --version | head -1 | cut -f2 -d' '
  • Since they were "almost all stolen! from various sources", why not quote these sources?

  • @Renan, now you set me up! I don’t know... http://www.catonmat.net/blog/perl-one-liners-explained-part-three/; this was born from a coffee discussion between friends -- It is easier my "not stolen" was the includes (but even this one surely someone remembered before). The monosyllables between the code are mine...

Browser other questions tagged

You are not signed in. Login or sign up in order to post.