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

17

Using the Math object of JavaScript, I can return a PI value with fixed decimal places, for example:

Math.PI //3.141592653589793

But in case I need (yes, quite unusual) to 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.
    
asked by anonymous 22.02.2015 / 22:38

3 answers

24

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

(and you want to be accountable for π, right? Well, if it were just a matter of displaying it, a pre-populated string with its value would suffice ...)

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

  • To be used in practice, it is preferable to get the value of π from somewhere instead of calculating it from π- it. A 300-byte string does not bring such a great overhead to your system ... (especially the extra code you would have to put in to do your calculation)
  • If it is really necessary (or desirable, for example for study purposes) to calculate for yourself, there are several formulas that converge to π. One of them (the first suggestion of the bigown answer ) is π = 48*arctan(1/18) + 32*arctan(1/57) − 20*arctan(1/239) , where the tangent arc can be calculated by middle of an infinite series. Other infinite series and / or expressions also converge to π.
  • I do not know which is the best (nor which is the best criterion to define "the best"), so I'll give you an example using the sine, as it seems 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 javascript-bignum library:

    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 code example above, precisao + 5 is to give a reasonable margin for convergence to occur (otherwise, smaller portions will be rounded to zero and will not enter the series). I put 70 decimal places, because it takes a lot to converge in the case of 300 (if you have the patience to wait, it should work, though).

        
    23.02.2015 / 05:01
    6

    Surely you would have to create your own algorithm and it is not that simple.

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

    Another code that I can maybe use as a reference.

    If I find anything else that can improve the answer I'll post it here.

        
    22.02.2015 / 22:45
    0

    Pi One-liner

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

    (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"
    

    I'm 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 PI of includes

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

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

    tex --version | head -1 | cut -f2 -d' '
    
        
    16.04.2015 / 11:12