Calculate π with quadratic convergence

20

3

Write a function or complete program that takes a positive number n and performs n steps of an iterative algorithm for calculating π that has quadratic convergence (i.e. it approximately doubles the number of accurate digits at every iteration) then returns or prints out 2n correct digits (including the beginning 3). One such algorithm is the Gauss–Legendre algorithm, but you are free to use a different algorithm if you prefer.

Examples:

input 1 → output 3.1
input 2 → output 3.141
input 5 → output 3.1415926535897932384626433832795

Requirements:

  • Each iteration of the algorithm must perform a constant number of basic operations such as addition, subtraction, multiplication, division, power and root (with integer exponent/degree) - each such operation on "big" integer/decimal numbers is counted as one even if it involves one or more loops internally. To be clear, trigonometric functions and powers involving complex numbers are not basic operations.
  • The algorithm is expected to have an initialization step which must also have a constant number of operations.
  • If the algorithm needs 1 or 2 more iterations to get to 2n correct digits, you can perform up to n+2 iterations instead of just n.
  • If it wasn't clear enough, after the correct 2n digits, your program must not print anything else (such as more correct digits, wrong digits or the complete works of Shakespeare).
  • Your program must support values of n from 1 to at least 20.
  • Your program should not take more than an hour for n=20 on a modern computer (not a hard rule, but try to keep it reasonable).
  • The program must not obtain more than 20 accurate digits after the initialization and first iteration of the algorithm.
  • The program must be runnable in Linux using freely available software.
  • The source code must use only ASCII characters.

Scoring:

Straightforward code golf, shortest code wins.

Winner:

The winner is Digital Trauma, I finally finished running his code on n=20 (just kidding). Special prize goes to primo for his very fast python solution and different algorithm :)

aditsu quit because SE is EVIL

Posted 2015-03-17T16:05:33.330

Reputation: 22 326

1Quadratic convergence is error ~ N^(1/2). What you describe is exponential convergence error ~ 2^(-N). – yo' – 2015-03-18T11:26:27.583

@yo' are you saying that wikipedia is wrong?

– aditsu quit because SE is EVIL – 2015-03-18T19:55:51.380

@yo' binary digits? What? No, you should compute pi in whatever form you want with 2^n correct decimal digits and output them in decimal representation. – aditsu quit because SE is EVIL – 2015-03-18T19:58:20.603

1Misleading, at least to say: "quadratic convergence" is ~q^(n^2) according to the 1st section there and ~q^2 according to the 2nd section there. – yo' – 2015-03-18T20:03:11.637

1I don't understand codegolf: surely anyone could just write their own programming language specifically for a single task like this, then write a program of, say, 0 bytes? – theonlygusti – 2015-03-18T20:52:34.390

2

@theonlygusti that would be a standard loophole and would get disqualified

– aditsu quit because SE is EVIL – 2015-03-18T20:54:55.643

@yo' I didn't change the conditions, I only clarified what should have been obvious but apparently wasn't. Also, your answer was not the only one that doesn't meet the requirements. – aditsu quit because SE is EVIL – 2015-03-25T10:16:07.167

@yo' exp(I*p).Imag() is just a long-winded way to write sin(p). That this was excluded is evident from the original problem statement. Why not just use acos(-1)? – primo – 2015-03-25T11:15:09.360

Answers

14

dc, 99 bytes

Golfed:

2?dsi1+^k1dddsa2v/sb4/stsp[lalb*vlalb+2/dla-d*lp*ltr-stsasblp2*spli1-dsi0<m]dsmxK2/1-klalb+d*4lt*/p

With whitespace and comments for "readability":

2?dsi               # Push 2. push input n, duplicate and store in i
1+^k                # Set calculation precision to 2^(n+1)
1dddsa              # Push four 1s. Store 1st in a
2v/sb               # Store 1/sqrt(2) in b
4/st                # Store 1/4 in t
sp                  # Store 1 in p
[                   # Start iteration loop macro
lalb*v              # Save sqrt(a*b) on stack
lalb+2/d            # Save a[i+1] = (a[i]+b[i])/2 on stack and duplicate
la-d*lp*ltr-        # Save t-p(a[i]-a[i+1])^2 on the stack
st                  # Store t result from stack
sa                  # Store a result from stack
sb                  # Store b result from stack
lp2*sp              # Store 2p in p
li1-dsi0<m]         # Decrement iteration counter i; recurse into macro if < 0
dsmx                # Duplicate, store and run macro
K2/1-k              # Set display precision to 2^n-1
lalb+d*4lt*/        # Save (a+b)^2/4t on stack
p                   # Print result

dc needs to be told how many digits of precision should be used. The calculation precision needs to be higher than the final display precision, so the calculation precision is set to be 2^(n+1) digits. I have verified the accuracy of the output with n=10 against http://www.angio.net/pi/digits/pi1000000.txt.

This slows down dramatically for larger n; n=12 takes 1.5 mins on my VM. Running a few different samples shows the time complexity is O(e^n) (not surprising). Extrapolating this to n=20 would have a runtime of 233 days. Oh well. Better than heat-death-of-the-universe at least.

This is moderately golfed - the stack is used to eliminate temporary variables during the calculations of each iteration, but there is possibly more use of the stack to shorten this more.

$ dc glpi.dc <<< 1
3.1
$ dc glpi.dc <<< 2
3.141
$ dc glpi.dc <<< 5
3.1415926535897932384626433832795
$ time dc glpi.dc <<< 7
3.1415926535897932384626433832795028841971693993751058209749445923078\
164062862089986280348253421170679821480865132823066470938446

real    0m0.048s
user    0m0.039s
sys 0m0.000s
$ 

If you don't like dc wrapping output at 70 chars, you can set the environment variable DC_LINE_LENGTH to 0:

$ DC_LINE_LENGTH=0 dc glpi.dc <<< 8
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648
$ 

Digital Trauma

Posted 2015-03-17T16:05:33.330

Reputation: 64 644

2Haha, "readability." Doesn't really apply to dc. ;) – Alex A. – 2015-03-17T18:23:37.243

It seems to print a lot more than 32 digits for input 5 – aditsu quit because SE is EVIL – 2015-03-18T00:12:10.520

I added a rule for that, plus another one about running time (but not really strict). I also don't like how your output is split into multiple lines with backslashes, is that a limitation of dc? – aditsu quit because SE is EVIL – 2015-03-18T01:09:10.877

I'm afraid the output is wrong for n=6 – aditsu quit because SE is EVIL – 2015-03-18T03:35:11.827

@aditsu Now I think it really should be fixed. Previously I was trying too hard to speed it up be keeping the precision lower for earlier iterations. This messed up the accuracy for later iterations. Now the precision is fixed high at the start (2^(n+1)) digits. This has the unexpected side-effect of speeding it up (no enough though). Also saved a char, but I still want to get it under 100. – Digital Trauma – 2015-03-18T04:57:57.220

1Great, and you got it under 100 too :) Could you also post the actual golfed 99-char program with no whitespace/comments? – aditsu quit because SE is EVIL – 2015-03-18T20:17:05.267

@aditsu golfed version added. – Digital Trauma – 2015-03-18T21:14:21.170

10

R, 156 bytes

Let's get this party started... with the absolute naivest implementation of the Gauss-Legendre algorithm ever.

for(i in 1:scan()){if(i<2){a=p=Rmpfr::mpfr(1,2e6);t=a/4;b=t^t}else{x=(a+b)/2;b=(a*b)^.5;t=t-p*(a-x)^2;a=x;p=2*p};o=(a+b)^2/(4*t)};cat(Rmpfr::format(o,2^i))

Ungolfed + explanation:

# Generate n approximations of pi, where n is read from stdin
for (i in 1:scan()) {

    # Initial values on the first iteration
    if (i < 2) {
        a <- p <- Rmpfr::mpfr(1, 1e7)
        t <- a/4
        b <- t^t
    } else {
        # Compute new values
        x <- (a + b) / 2
        b <- (a*b)^0.5
        t <- t - p*(a - x)^2

        # Store values for next iteration
        a <- x
        p <- 2*p
    }

    # Approximate pi 
    o <- (a + b)^2 / (4*t)
}

# Print the result with 2^n digits
cat(Rmpfr::format(o, 2^i))

The mpfr() function is part of the Rmpfr package. It creates an mpfr object using the first argument as the value and the second argument as the number of bits of precision. We assign a and p to 1, and by defining t based on a (and b based on t), the mpfr type propogates to all four variables, thereby maintaining precision throughout.

As mentioned, this requires the R package Rmpfr, which is an acronym for "R Multiple Precision Floating point Reliable." The package uses GMP in the background. Unfortunately base R does not have the ability to do high-precision arithmetic, hence the package dependency.

Don't have Rmpfr? No sweat. install.packages("Rmpfr") and all of your dreams will come true.

Notice that 2e6 was specified as the precision. That means we have 2,000,000 bits of precision, which is enough to maintain precision for at least n = 20. (Note: n = 20 takes a long time but less than an hour on my computer.)

The approach here is literally just a regurgitation of the formulas on the Wikipedia page, but hey, we have to start somewhere.

Any input is welcome as always!


I had to rewrite a lot of this but I still have to acknowledge that Peter Taylor helped me knock 70 bytes off of my first score. In the words of DigitalTrauma, "boom."

Alex A.

Posted 2015-03-17T16:05:33.330

Reputation: 23 761

7

Python 2, 214 bytes

This challenge presented a good excuse for me to learn the Decimal module. The Decimal numbers have definable precision and have square root support. I have set the precision to a conservative estimate of the accuracy depending on the loop count.

Update

I have updated the program to improve accuracy and speed, at the expense of golfing. By using the decimal sqrt() method and replacing the x**2 usage with x*x, it is now 200 times faster. This means it can now compute 20 loops giving a million digit result in 6.5 hours. The Decimal numbers often have an error in the last digit (caused by operations on the limit of precision), so the program now uses and discards 5 extra digits so only accurate digits are printed.

from decimal import*
d=Decimal
e=input()
getcontext().prec=5+(1<<e)
k=d(1)
j=d(2)
g=j*j
h=k/j
a=k
b=k/j.sqrt()
t=k/g
p=k
for i in[0]*e:f=a;a,b=(a+b)/j,(a*b).sqrt();c=f-a;t-=c*c*p;p+=p
l=a+b
print str(l*l/g/t)[:-5]

Sample run:

$ echo 1 | python min.py 
3.1
$ echo 2 | python min.py 
3.141
$ echo 3 | python min.py 
3.1415926
$ echo 5 | python min.py 
3.1415926535897932384626433832795
$ echo 12 | python min.py
3.141592653589793238462643383279502884197169399375105820974944592307816406286208
99862803482534211706798214808651328230664709384460955058223172535940812848111745
02841027019385211055596446229489549303819644288109756659334461284756482337867831
65271201909145648566923460348610454326648213393607260249141273724587006606315588
17488152092096282925409171536436789259036001133053054882046652138414695194151160
94330572703657595919530921861173819326117931051185480744623799627495673518857527
24891227938183011949129833673362440656643086021394946395224737190702179860943702
77053921717629317675238467481846766940513200056812714526356082778577134275778960
91736371787214684409012249534301465495853710507922796892589235420199561121290219
60864034418159813629774771309960518707211349999998372978049951059731732816096318
59502445945534690830264252230825334468503526193118817101000313783875288658753320
83814206171776691473035982534904287554687311595628638823537875937519577818577805
32171226806613001927876611195909216420198938095257201065485863278865936153381827
96823030195203530185296899577362259941389124972177528347913151557485724245415069
59508295331168617278558890750983817546374649393192550604009277016711390098488240
12858361603563707660104710181942955596198946767837449448255379774726847104047534
64620804668425906949129331367702898915210475216205696602405803815019351125338243
00355876402474964732639141992726042699227967823547816360093417216412199245863150
30286182974555706749838505494588586926995690927210797509302955321165344987202755
96023648066549911988183479775356636980742654252786255181841757467289097777279380
00816470600161452491921732172147723501414419735685481613611573525521334757418494
68438523323907394143334547762416862518983569485562099219222184272550254256887671
79049460165346680498862723279178608578438382796797668145410095388378636095068006
42251252051173929848960841284886269456042419652850222106611863067442786220391949
45047123713786960956364371917287467764657573962413890865832645995813390478027590
09946576407895126946839835259570982582262052248940772671947826848260147699090264
01363944374553050682034962524517493996514314298091906592509372216964615157098583
87410597885959772975498930161753928468138268683868942774155991855925245953959431
04997252468084598727364469584865383673622262609912460805124388439045124413654976
27807977156914359977001296160894416948685558484063534220722258284886481584560285
06016842739452267467678895252138522549954666727823986456596116354886230577456498
03559363456817432411251507606947945109659609402522887971089314566913686722874894
05601015033086179286809208747609178249385890097149096759852613655497818931297848
21682998948722658804857564014270477555132379641451523746234364542858444795265867
82105114135473573952311342716610213596953623144295248493718711014576540359027993
44037420073105785390621983874478084784896833214457138687519435064302184531910484
81005370614680674919278191197939952061419663428754440643745123718192179998391015
91956181467514269123974894090718649423196156794520809514655022523160388193014209
37621378559566389377870830390697920773467221825625996615014215030680384477345492
02605414665925201497442850732518666002132434088190710486331734649651453905796268
56100550810665879699816357473638405257145910289706414011097120628043903975951567
71577004203378699360072305587631763594218731251471205329281918261861258673215791
98414848829164470609575270695722091756711672291098169091528017350671274858322287
18352093539657251210835791513698820914442100675103346711031412671113699086585163
98315019701651511685171437657618351556508849099898599823873455283316355076479185
35893226185489632132933089857064204675259070915481416549859461637180270981994309
92448895757128289059232332609729971208443357326548938239119325974636673058360414
28138830320382490375898524374417029132765618093773444030707469211201913020330380
19762110110044929321516084244485963766983895228684783123552658213144957685726243
34418930396864262434107732269780280731891544110104468232527162010526522721116603
96665573092547110557853763466820653109896526918620564769312570586356620185581007
29360659876486117

The ungolfed code:

from decimal import *
d = Decimal

loops = input()
# this is a conservative estimate for precision increase with each loop:
getcontext().prec = 5 + (1<<loops)

# constants:
one = d(1)
two = d(2)
four = two*two
half = one/two

# initialise:
a = one
b = one / two.sqrt()
t = one / four
p = one

for i in [0]*loops :
    temp = a;
    a, b = (a+b)/two, (a*b).sqrt();
    pterm = temp-a;
    t -= pterm*pterm * p;
    p += p

ab = a+b
print str(ab*ab / four / t)[:-5]

Logic Knight

Posted 2015-03-17T16:05:33.330

Reputation: 6 622

4Heh half = one/two – Digital Trauma – 2015-03-17T19:18:30.767

It seems that you're not printing the correct number of digits. And I wonder if the slowness is due to the unnecessary use of **. – aditsu quit because SE is EVIL – 2015-03-18T00:19:25.510

1@aditsu, I have reduced the accuracy to expected digit count (but throwing away perfectly good accuracy from an iteration is making my teeth itch). Good suggestion on the ** effect. I found lots of speed by getting rid of them. I can't meet the 20 loops in 1 hour though. Perhaps with pypy or Cython? Hmmm. I will consider that. – Logic Knight – 2015-03-18T09:45:35.403

Much better :) For this problem, throwing away good accuracy is less evil than continuing into bad accuracy. The 1 hour limit is based on my cjam/java test code run with java 8. Maybe python doesn't have efficient multiplication/division/sqrt for large numbers (Karatsuba & co)? – aditsu quit because SE is EVIL – 2015-03-18T20:14:22.020

@aditsu: I believe integers have karatsuba (and just that) -- but with 32-bit limb size rather than 64-bit limb size. Who knows about Decimal. – None – 2015-03-21T22:24:51.600

5

Python (2.7) - 131 bytes

from gmpy import*
n=input()
p=a=fsqrt(mpf(8,4<<n));b=0
exec"a=fsqrt(a/2);b=1/(a-a*b+b/a+1);p*=b+a*a*b;a+=1/a;"*n
print`p`[5:2**n+6]

Update: Now using gmpy rather than gmpy2. For whatever reason, in gmpy2 setting the precision on a single value doesn't propagate to other values. The result of any calculation reverts back to the precision of the current context. Precision does propagate in gmpy, which seems more intuitive to me. It's also considerably less verbose.

Using one of the many algorithms devised by Borwein and Borwein, slightly refactored. n=20 takes about 11 seconds on my box. Not the most efficient method, but still not bad.


Refactoring

The original algorithm was the following:




Refactoring was done incrementally, but the end result is that




The major simplification happens in pn+1. It's also slightly faster due to having eliminated a division.

The current implementation pushes an back one iteration in the calculation of pn+1, which allows for a different initialization of p0 (2√2), but is otherwise identical.


Sample Usage

$ echo 1 | python pi-borwein.py
3.1

$ echo 2 | python pi-borwein.py
3.141

$ echo 5 | python pi-borwein.py
3.1415926535897932384626433832795

$ echo 10 | python pi-borwein.py
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198938095257201065485863278

primo

Posted 2015-03-17T16:05:33.330

Reputation: 30 891

Great, but you're missing a digit for n=7 – aditsu quit because SE is EVIL – 2015-03-18T22:42:21.547

Also, is it this algorithm?

– aditsu quit because SE is EVIL – 2015-03-18T22:51:05.623

@aditsu fixed, and yes it is. – primo – 2015-03-18T22:52:06.893

Now the last digit is wrong for n=5 – aditsu quit because SE is EVIL – 2015-03-18T22:54:29.167

@aditsu gmpy takes precision as bits, unfortunately. Should be correct for all values now, with a minor loss of efficiency. – primo – 2015-03-18T23:00:31.150

The program with gmpy2 worked great, but now it seems that I can't import gmpy. Too old? – aditsu quit because SE is EVIL – 2015-03-25T00:26:16.477

@aditsu it will need to be installed separately. – primo – 2015-03-25T03:45:17.960

It doesn't seem to be available (anymore) in my distro. I mean.. there is a gmpy package, but oldest version is 2.0.3 (which is gmpy2). Could you provide some generic installation instructions? – aditsu quit because SE is EVIL – 2015-03-25T04:26:21.177

1@aditsu pip install gmpy worked for me; gmpy and gmpy2 are separate packages. However, it does rely on the deprecated distutils. – primo – 2015-03-25T07:26:38.013

3

bc and Newton's method, 43 bytes

Newton's method for finding zeros of any function converges quadratically and the algorithm is way simpler than for Gauss-Legendre. It basically boils down to:

xnew = xold - f(xold)/f'(xold)

So here goes the according snippet:

n=20;x=3;scale=2^n;while(n--)x-=s(x)/c(x);x

A bit more readable:

/* desired number of iterations */
n = 20;

/* starting estimate for pi */
x = 3;

/* set precision to 2^n */
scale = 2^n;

/* perform n iteration steps */
while(n--)
  // f:=sin, f'=cos
  x -= s(x)/c(x)

To test this, run bc -l in a shell and paste the above snippet. Be prepared to wait for a while; n=20 has been running for about 5min now and no end in sight, yet. n=10 takes about 40s.

Niklaus Messerli

Posted 2015-03-17T16:05:33.330

Reputation: 61

4Not sure if sine and cosine qualify as "basic operations such as addition, subtraction, multiplication, division and power (including roots)". However, if you rolled your own sine/cosine, that would probably be acceptable. – primo – 2015-03-20T08:13:09.317

1Neat formula, though -- it says that π is a fixed point of f(x) = x - tan(x) – Casey Chu – 2015-03-22T07:38:57.587