1

My code:

#include <iostream>
#include <iomanip>
#include <cmath>

long double fac(long double num) {
    long double result = 1.0;
    for (long double i=2.0; i<num; i++)
       result *= i;
    return result;
}

int main() {
    using namespace std;
    long double pi=0.0;
    for (long double k = 0.0; k < 10.0; k++) {
        pi += (pow(-1.0,k) * fac(6.0 * k) * (13591409.0 + (545140134.0 * k))) 
            / (fac(3.0 * k) * pow(fac(k), 3.0) * pow(640320.0, 3.0 * k + 3.0/2.0));
    }
    pi *= 12.0;
    cout << setprecision(100) << 1.0 / pi << endl;
    return 0;
}

My output:

3.1415926535897637228433865175247774459421634674072265625

The problem with this output is that it outputed 56 digits instead of 100; How do I fix that?

FFmpegEnthusiast
  • 193
  • 1
  • 10
  • 8
    You can't. `double`s cannot hold 100 digits of precision. You'll have to find some arbitrary precision math library, of some kind, and rewrite this to use the library. Unfortunately, it is not appropriate to recommend any specific software libraries, on Stackoverflow. – Sam Varshavchik Aug 06 '22 at 01:31
  • @SamVarshavchik there are no `double`s there are `long double`s tho – FFmpegEnthusiast Aug 06 '22 at 01:37
  • I'll amend my comment to say the same, about `long double`s. They cannot hold 100 digits of precision. The rest of my comment still applies. – Sam Varshavchik Aug 06 '22 at 01:38
  • `pow(-1.0,k)` -- These can be in a table of constants instead of incurring a penalty calling `pow` repeatedly. But really, this code is broken when you want to use `double` or `long double` for this type of precision. Programs using `double` have issues with just 15 digits of precision, and you want 100 digits, given all of those calls to floating point functions -- don't think that will work. – PaulMcKenzie Aug 06 '22 at 01:49
  • @TheRandomGuyNamedJoe12 The overall issue I believe is that you are taking a shoolbook math formula, and hoping that translating directly to a machine that works in binary will give you the same results as if you calculated this by hand. That is almost never the case -- that's why many formulas are rewritten to best handle the error propagation that may/will result with binary floating point values. – PaulMcKenzie Aug 06 '22 at 01:55
  • You might want to check out https://cplusplus.com/forum/beginner/210445/. You can use boost's multiprecision to achieve your goal. Here's a simple example: cpp.sh/8lmfw . You can try using gmp library also but it might be a bit complex if you're a beginner. – Shubham Garg Aug 06 '22 at 02:04
  • *Quick estimate:* A 32-bit integer can almost represent 10 decimal digits. To get (over) ten times that, you need somewhere around 320 bits, probably more. Ballpark, just an estimate to get a feel for the scale involved. Is your `long doubble` over 300 bits in size? (The standard does not impose an upper limit, so it is theoretically possible...) – JaMiT Aug 06 '22 at 02:06
  • You might find this interesting: https://github.com/Mysticial/Mini-Pi – Jerry Coffin Aug 06 '22 at 04:23
  • 2
    See https://gmplib.org if you _really_ need this kind of precision. But also note that your pi is _very off_ right now. Pi starts as 14, 15, 92, 65, 35 (two 5 endings, nice), 89, 79 (two 9 endings, nice). Yours goes wrong there and has 89, 76 - you're running into floating point precision ruining the party not at the 57th decimal place, but the 14th. Never mind getting 100 decimal places, you're already not making it to 15. – Mike 'Pomax' Kamermans Aug 06 '22 at 04:59

2 Answers2

1

First of all your factorial is wrong the loop should be for (long double i=2.0; i<=num; i++) instead of i<num !!!

As mentioned in the comments double can hold only up to ~16 digits so your 100 digits is not doable by this method. To remedy this there are 2 ways:

  1. use high precision datatype

    there are libs for this, or you can implement it on your own you need just few basic operations. Note that to represent 100 digits you need at least

    ceil(100 digits/log10(2)) = 333 bits
    

    of mantisa or fixed point integer while double has only 53

    53*log10(2) = 15.954589770191003346328161420398 digits
    
  2. use different method of computation of PI

    For arbitrary precision I recommend to use BPP However if you want just 100 digits you can use simple taylor seriesbased like this on strings (no need for any high precision datatype nor FPU):

     //The following 160 character C program, written by Dik T. Winter at CWI, computes pi  to 800 decimal digits. 
     int a=10000,b=0,c=2800,d=0,e=0,f[2801],g=0;main(){for(;b-c;)f[b++]=a/5;
     for(;d=0,g=c*2;c-=14,printf("%.4d",e+d/a),e=d%a)for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);}
    

Aside the obvious precision limits Your implementation is really bad from both performance and precision aspects that is why you lost precision way sooner as you hitting double precision limits in very low iterations of k. If you rewrite the iterations so the subresults are as small as can be (in terms of bits of mantisa) and not use too much unnecessary computations here few hints:

  1. why are you computing the same factorials again and again

    You have k! in loop where k is incrementing why not just multiply the k to some variable holding actual factorial instead? for example:

    //for (    k=0;k<10;k++){              ... fac(k) ... }
      for (f=1,k=0;k<10;k++){ if (k) f*=k; ... f      ... }
    
  2. why are you divide by factorials again and again

    if you think a bit about it then if (a>b) you can compute this instead:

    a! / b! = (1*2*3*4*...*b*...*a) / (1*2*3*4*...*b)
    a! / b! = (b+1)*(b+2)*...*(a)
    
  3. I would not use pow at all for this

    pow is "very complex" function causing further precision and performance losses for example pow(-1.0,k) can be done like this:

    //for (     k=0;k<10;k++){       ... pow(-1.0,k) ... }
      for (s=+1,k=0;k<10;k++){ s=-s; ... s           ... }
    

    Also pow(640320.0, 3.0 * k + 3.0/2.0)) can be computed in the same way as factorial, pow(fac(k), 3.0) you can 3 times multipply the variable holding fac(k) instead ...

  4. the therm pow(640320.0, 3.0 * k + 3.0/2.0) outgrows even (6k)!

    so you can divide it by it to keep subresults smaller...

These few simple tweaks will enhance the precision a lot as you will overflow the double precision much much latter as the subresults will be much smaller then the naive ones as factorials tend to grow really fast

Putting all together leads to this:

double pi_Chudnovsky() // no pow,fac lower subresult
    {                   // https://en.wikipedia.org/wiki/Chudnovsky_algorithm
    double pi,s,f,f3,k,k3,k6,p,dp,q,r;
    for (pi=0.0,s=1.0,f=f3=1,k=k3=k6=0.0,p=640320.0,dp=p*p*p,p*=sqrt(p),r=13591409.0;k<27.0;k++,s=-s)
        {
        if (k)  // f=k!,  f3=(3k)!, p=pow(640320.0,3k+1.5)*(3k)!/(6k)!, r=13591409.0+(545140134.0*k)
            {
            p*=dp; r+=545140134.0;
            f*=k; k3++; f3*=k3; k6++; p/=k6; p*=k3;
                  k3++; f3*=k3; k6++; p/=k6; p*=k3;
                  k3++; f3*=k3; k6++; p/=k6; p*=k3;
                                k6++; p/=k6;
                                k6++; p/=k6;
                                k6++; p/=k6;
            }
        q=s*r; q/=f; q/=f; q/=f; q/=p; pi+=q;
        }
    return 1.0/(pi*12.0);
    }

as you can see k goes up to 27, while your naive method can go only up to 18 on 64 bit doubles before overflow. However the result is the same as the double mantissa is saturated after 2 iterations ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • pi.cpp: In function ‘double pi_Chudnovsky()’: pi.cpp:3:32: error: ‘pow’ was not declared in this scope 3 | double pi,s,f,f3,k,k3,k6,p=pow(640320.0,1.5),dp=pow(640320.0,3.0); | ^~~ pi.cpp:8:16: error: ‘dp’ was not declared in this scope; did you mean ‘p’? 8 | p*=dp; | ^~ | p – FFmpegEnthusiast Aug 06 '22 at 15:19
  • Mine as said in question uses `long double` my method gave 56 digits as mentioned in the question – FFmpegEnthusiast Aug 06 '22 at 15:24
  • @TheRandomGuyNamedJoe12 have you checked *how many* of those 56 digits are actually correct? I'd say 14, counting the 3. – Bob__ Aug 06 '22 at 17:01
  • @TheRandomGuyNamedJoe12 compiler that I use is pretty old so `long double` and `double` are the same 64bit ... if your compiler does not know `pow` how did yo compile your code? just include `math.h` or whatever you got maybe you use some weird compiler environment like gcc and need to use `std::pow` ... the source is compilable and working so if its not on your end you did something wrong – Spektre Aug 06 '22 at 17:06
  • @Spektre of course some are incorrect my factorial function was incorrect – FFmpegEnthusiast Aug 07 '22 at 00:18
  • Where is the defintion of `main` – FFmpegEnthusiast Aug 07 '22 at 00:27
  • @TheRandomGuyNamedJoe12 its only a function not complete program (only the important stuff) ... obviously just copy the `pi_Chudnovsky()` function to your current program before main and add `cout << setprecision(100) << pi_Chudnovsky() << endl;` to your main – Spektre Aug 07 '22 at 05:10
  • @TheRandomGuyNamedJoe12 The usual 80-bit `long double` has 19 decimal digits of precision, and if you correct the factorial problem, you will get about 19 correct digits. The rest is just digital noise. – n. m. could be an AI Aug 07 '22 at 10:59
  • @TheRandomGuyNamedJoe12 btw I played with the chungovsky algo trying to port it to big integer/fixed point to go beond double limitations however it really needs high precision floating point as the `pow(640320.0,3k+1.5)*(3k)!/(6k)!` grows insanely fast and any rounding there affect the result greatly so I would stick to the Taylor up to 800 digits (its really fast and does not need anything just beware the original does not reset some variables to zero causing problems on different compilers I updated code with remedy) and for anything above use BPP along with bigint/fixed point – Spektre Aug 07 '22 at 17:17
-2

I am feeling happy due to following code :)

/*
    I have compiled using cygwin
    change "iostream...using namespace std" OR iostream.h based on your compiler at related OS.
*/
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
long double fac(long double num)
{
    long double result = 1.0;
    for (long double i=2.0; num > i; ++i)
    {
        result *= i;
    }
    return result;
}
int main()
{
    long double pi=0.0;
    for (long double k = 0.0; 10.0 > k; ++k)
    {
        pi += (pow(-1.0,k) * fac(6.0 * k) * (13591409.0 + (545140134.0 * k)))
        / (fac(3.0 * k) * pow(fac(k), 3.0) * pow(640320.0, 3.0 * k + 3.0/2.0));
    }
    pi *= 12.0;
    cout << "BEFORE USING setprecision VALUE OF DEFAULT PRECISION " << cout.precision() << "\n";
    cout << setprecision(100) << 1.0 / pi << endl;
    cout << "AFTER  USING setprecision VALUE OF CURRENT PRECISION   WITHOUT USING fixed " << cout.precision() << "\n";
    cout << fixed;
    cout << "AFTER  USING setprecision VALUE OF CURRENT PRECISION   USING fixed " << cout.precision() << "\n";
    cout << "USING fixed PREVENT THE EARTH'S ROUNDING OFF INSIDE OUR UNIVERSE :)\n";
    cout << setprecision(100) << 1.0 / pi << endl;
    return 0;
}
/*
$ # Sample output:
$ g++  73256565.cpp -o ./a.out;./a.out
$ ./a.out
BEFORE USING setprecision VALUE OF DEFAULT PRECISION 6
3.14159265358976372457810999350158454035408794879913330078125
AFTER   USING setprecision VALUE OF CURRENT PRECISION   WITHOUT USING fixed 100
AFTER   USING setprecision VALUE OF CURRENT PRECISION   USING fixed 100
USING fixed PREVENT THE EARTH'S ROUNDING OFF INSIDE OUR UNIVERSE :)
3.1415926535897637245781099935015845403540879487991333007812500000000000000000000000000000000000000000
*/
  • 2
    This may print more digits, but they won't be accurate digits of pi, since `long double` still can only represent a certain set of rational numbers. – aschepler Aug 06 '22 at 11:20