In this article, we will examine some interesting methods to calculate Pi value. This is a special article that I prepared to celebrate the Pi day, which is March 14th.
Since Python has a built-in pi value in the math module, we can always use it. Python uses double precision to store the values, and double precision numbers have 53 bits (16 digits) of precision. To understand this more, see the code below.
import math
format(math.pi, ".16f"), format(math.pi, ".50f"), format(math.pi, ".100f")
('3.1415926535897931',
'3.14159265358979311599796346854418516159057617187500',
'3.1415926535897931159979634685441851615905761718750000000000000000000000000000000000000000000000000000')
It looks like that there are 48 digits after the decimal point when we try to print 100 digits, but it may not be accurate after 16 digits! In fact, the value is wrong, because the 16th digit should be 2, yet it is shown as 1 above. To better understand this behavior, we can do a simple experiment below. You will see that the digits in Python representation of 0.1 can be random ones after 16 digits.
from decimal import Decimal, getcontext
Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
If we however know the true value of $\pi$, we can represent it using decimal module instead.
from decimal import Decimal
a = Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679')
print ("pi=", a)
pi= 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679
Below I describe several methods that can be used to calculate $\pi$ values. For each method, I first calculate it as floating point value, so the actual value calculted may not be accurate after 16th digit. After that, I will re-do the calculation using the decimal module to get higher precision.
Ramanujan is an Indian mathematician. He left over 3000 equations to the mankind. His formula to calculate $\pi$ is below:
$\frac{1}{\pi} = \frac{\sqrt{8}}{9801} \displaystyle\sum_{n-0}^{\infty} \frac{(4n)! * (1103+26390n)}{(n!)^4 396^{4n}} $
This equation looks so alien to me. So let's first use the violent way to validate it before going any further.
import math
from math import factorial
pi_inv = 0
part1 = math.sqrt(8)/9801;
for i in range(10):
increase = part1 * factorial(4*i) * (1103+26390*i)/factorial(i)**4 / 396**(4*i)
pi_inv = pi_inv + increase
print ("at cycle ", i, " increase=", format(increase, ".20e"), " pi_inv=", format(pi_inv, ".20f"), " pi=", format(1/pi_inv, ".20f"))
at cycle 0 increase= 3.18309878440470150895e-01 pi_inv= 0.31830987844047015090 pi= 3.14159273001330552333 at cycle 1 increase= 7.74332048352151317709e-09 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 2 increase= 6.47985705171743734299e-17 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 3 increase= 5.75749844947969743470e-25 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 4 increase= 5.30811167108277030133e-33 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 5 increase= 5.00950932875506003031e-41 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 6 increase= 4.80364919393929934556e-49 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 7 increase= 4.65962442292405811047e-57 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 8 increase= 4.55943215233673498630e-65 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600 at cycle 9 increase= 4.49184777126218565070e-73 pi_inv= 0.31830988618379069122 pi= 3.14159265358979311600
What can you say? He is like a bug in human system. Nobody knows where this equation comes from or how to explain it and of course he did not give an explanation because he died so early in his age (32 years old).
But let's just be realistic here. If he was born in a rich European family rather than India, then he is probably a famous mathematician in textbooks (even if he still died at 32 years old). He had no formal training at all, and many of his mathematical inventions were already known oustdie of India in his time.
In his time, there is no computer. So he created all these formula in his head. Nowadays, with computers, people are trying to re-create these fancy equations by letting the computer find it by itself. There is a website for doing that.
Next, we try to do the same calculation using decimal module with a 100 digit accuray
from math import factorial
from decimal import Decimal, getcontext
getcontext().prec = 100 #set 100 digits precision
pi_inv = Decimal(0) #convert these numbers all to Decimal data types
part1 = Decimal(8).sqrt() / Decimal(9801)
for i in range(10):
increase = part1 * ((Decimal(factorial(4 * i))) * (Decimal(1103) + Decimal(26390) * Decimal(i))) / ( (Decimal(factorial(i)))**4 * (Decimal(396)**(4*i)))
pi_inv += increase
print ("at cycle ", i, " increase=", format(increase, ".20e"), " pi_inv=", format(pi_inv, ".20f"), " pi=", format(pi_inv**(-1), ".20f"))
1/pi_inv
at cycle 0 increase= 3.18309878440470123218e-1 pi_inv= 0.31830987844047012322 pi= 3.14159273001330566031 at cycle 1 increase= 7.74332048352151198064e-9 pi_inv= 0.31830988618379060674 pi= 3.14159265358979387800 at cycle 2 increase= 6.47985705171743502429e-17 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 3 increase= 5.75749844947969611364e-25 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 4 increase= 5.30811167108276904702e-33 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 5 increase= 5.00950932875505841976e-41 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 6 increase= 4.80364919393929826316e-49 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 7 increase= 4.65962442292405812754e-57 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 8 increase= 4.55943215233673400909e-65 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 9 increase= 4.49184777126218441259e-73 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846
Decimal('3.141592653589793238462643383279502884197169399375105820974944592307816406286209042542808018278896595')
Chudnovsky formula is a variant of the Ramanujan formula.
$\frac{1}{\pi} = \frac{1}{53360\sqrt{640320}} \displaystyle\sum_{k=0}^{\infty} (-1)^k \frac{(6k)!}{(k!)^3 (3k)!} \times \frac{13591409+545140134k}{640320^{3k}} $
It feels as alien as Ramanujan, but let's test it in the violent way:
from math import factorial
pi_inv = 0
part1 = 1/math.sqrt(640320)/53360;
for i in range(10):
increase = part1 * (-1)**i * factorial(6*i) / factorial(i)**3 / factorial(3*i) * (13591409 + 545140134*i) / 640320**(3*i)
pi_inv = pi_inv + increase
print ("at cycle ", i, " increase=", format(increase, ".20e"), " pi_inv=", format(pi_inv, ".20f"), " pi=", format(1/pi_inv, ".20f"))
at cycle 0 increase= 3.18309886183796630910e-01 pi_inv= 0.31830988618379663091 pi= 3.14159265358973449622 at cycle 1 increase= -5.98106993865707316163e-15 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 2 increase= 3.11915039112124748203e-29 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 3 increase= -1.74325149176513019055e-43 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 4 increase= 1.01349712778174201820e-57 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 5 increase= -6.03788321488688115840e-72 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 6 increase= 3.65675095016945421368e-86 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 7 increase= -2.24099151583600967748e-100 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 8 increase= 1.38562943809251026279e-114 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009 at cycle 9 increase= -8.62706484778169674443e-129 pi_inv= 0.31830988618379063571 pi= 3.14159265358979356009
As you will see, the convergence is actually faster than Ramanujan formula. At cycle 7, the modification to $1/\pi$ is already in the 1e-100 range.
Basically, each cycle yield approximately 14 additional effective digits (see the change from 1 to 15 to 29 to 43 to 57 above). This formula was previously used to calculate world record of Pi precision. Yet in Ramanujan formula, only 8 digits are generated for each cycle. I know we are talking about $1/\pi$ rather than $\pi$ directly, but the principles of the precision of inverting a number should be the same.
Below is the more precise version using decimal module:
from math import factorial
getcontext().prec = 100 #set 100 digits precision
pi_inv = Decimal(0)
part1 = Decimal(1)/Decimal(640320).sqrt()/Decimal(53360);
for i in range(10):
increase = part1 * (-1)**i * Decimal(factorial(6*i)) / Decimal(factorial(i)**3) / Decimal(factorial(3*i)) * (Decimal(13591409) + Decimal(545140134)*Decimal(i)) / Decimal(640320**(3*i))
pi_inv = pi_inv + increase
print ("at cycle ", i, " increase=", format(increase, ".20e"), " pi_inv=", format(pi_inv, ".20f"), " pi=", format(1/pi_inv, ".20f"))
1/pi_inv
at cycle 0 increase= 3.18309886183796652608e-1 pi_inv= 0.31830988618379665261 pi= 3.14159265358973420767 at cycle 1 increase= -5.98106993865707405238e-15 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 2 increase= 3.11915039112124831996e-29 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 3 increase= -1.74325149176513054149e-43 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 4 increase= 1.01349712778174211965e-57 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 5 increase= -6.03788321488688112436e-72 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 6 increase= 3.65675095016945436402e-86 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 7 increase= -2.24099151583600984249e-100 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 8 increase= 1.38562943809251044103e-114 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846 at cycle 9 increase= -8.62706484778169809948e-129 pi_inv= 0.31830988618379067154 pi= 3.14159265358979323846
Decimal('3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067')
People who play with CPU benchmarking and overclocking should be very familiar with the program. I use it a lot and I consider it the best proxy for computational power in bioinformatics research even though it is not an integer algorithm.
In previous Methods we see that infinite series typically increase the number of correct digits additively in successive terms. However, iterative algorithms generally multiply the number of correct digits at each step, so it is preferred over infinite series.
Based on its wiki page, SuperPi uses Gauss–Legendre algorithm and it calculates the decimal point up to a maximum of 32 million. Perhaps the best explanation that I can find online is given here, which describes many algorithms including the Gauss–Legendre algorithm. Basically it contains the following steps:
1, Initialize: let $a_0=1$, $b_0=1/\sqrt{2}$, $t_0=1/4$, $p_0=1$
2, Iterate: $a_{n+1}=\frac{a_n+b_n}{2}$, $b_{n+1}=\sqrt{a_nb_n}$, $t_{n+1}=t_n-p_n(a_n-a_{n+1})^2$, $p_{n+1}=2p_n$
3, Calculate $\pi=\frac{(a_n+b_n)^2}{4t_n}$
Again, let's use violent way to do test it:
import math
a = 1
b = 1/math.sqrt(2)
t = 1/4
p = 1
pi = 0
for i in range(10):
olda, oldb, oldt, oldp, oldpi = a, b, t, p, pi
a = (olda+oldb)/2
b = math.sqrt(olda*oldb)
t = oldt - oldp*(olda-a)**2
p = 2*oldp
pi = (olda+oldb)**2/4/oldt
print ("at cycle ", i, " increase=", format(pi-oldpi, ".20e"), " pi=", format(pi, ".20f"))
pi
at cycle 0 increase= 2.91421356237309492343e+00 pi= 2.91421356237309492343 at cycle 1 increase= 2.26365688149073651658e-01 pi= 3.14057925052216857509 at cycle 2 increase= 1.01339569137426366296e-03 pi= 3.14159264621354283875 at cycle 3 increase= 7.37625116542517389462e-09 pi= 3.14159265358979400418 at cycle 4 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418 at cycle 5 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418 at cycle 6 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418 at cycle 7 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418 at cycle 8 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418 at cycle 9 increase= 0.00000000000000000000e+00 pi= 3.14159265358979400418
3.141592653589794
It looks like the convergence already reached limit of double digit precision at cycle 4 above.
We next use the decimal module to calculate a more precise value for $\pi$ below. At cycle 7, the increase becomes zero, I think this is an indication that it already reached 100-digit precision at cycle 7. You can change the precision to 300 and see how results change.
import math
getcontext().prec = 100 #set 100 digits precision
a = Decimal(1)
b = Decimal(1)/Decimal(2).sqrt()
t = Decimal(1/4)
p = Decimal(1)
pi = Decimal(0)
for i in range(10):
olda, oldb, oldt, oldp, oldpi = a, b, t, p, pi
a = (olda+oldb)/Decimal(2)
b = Decimal(olda*oldb).sqrt()
t = oldt - oldp*(olda-a)**2
p = Decimal(2)*oldp
pi = (olda+oldb)**2/Decimal(4)/oldt
print ("at cycle ", i, " increase=", format(pi-oldpi, ".20e"), " pi=", format(pi, ".20f"))
pi
at cycle 0 increase= 2.91421356237309504880e+0 pi= 2.91421356237309504880 at cycle 1 increase= 2.26365688149073199510e-1 pi= 3.14057925052216824831 at cycle 2 increase= 1.01339569137403383801e-3 pi= 3.14159264621354228215 at cycle 3 increase= 7.37625095613016834282e-9 pi= 3.14159265358979323828 at cycle 4 increase= 1.83130608477638909816e-19 pi= 3.14159265358979323846 at cycle 5 increase= 5.47210914568994183275e-41 pi= 3.14159265358979323846 at cycle 6 increase= 2.40612213044503300000e-84 pi= 3.14159265358979323846 at cycle 7 increase= 0.00000000000000000000e-79 pi= 3.14159265358979323846 at cycle 8 increase= 0.00000000000000000000e-79 pi= 3.14159265358979323846 at cycle 9 increase= 0.00000000000000000000e-79 pi= 3.14159265358979323846
Decimal('3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067')
This is a more modern algorithm that can achieve Nonic convergence, and because it is iterative algorithm, the correct digits are multiplied (rather than linear increase) with each iteration. In other words, each iteration approximately multiplies the number of correct digits by nine.
I copied the Nonic convergence formula from wiki.
1, Initiliaze: let $a_0=1/3$, $r_0=(\sqrt{3}-1)/2$, $s_0=(1-r_0^3)^{1/3}$
2, Iterate: let $t_{n+1}=1+2r_n$, $u_{n+1}=(9r_n(1+r_n+r_n^2))^{1/3}$, $v_{n+1}=t_{n+1}^2+t_{n+1}u_{n+1}+u_{n+1}^2$, $w_{n+1}=\frac{27(1+s_n+s_n^2)}{v_{n+1}}$, $a_{n+1}=w_{n+1}a_n+3^{2n-3}(1-w_{n+1})$, $s_{n+1}=\frac{(1-r_n)^3}{(t_{n+1}+2u_{n+1})v_{n+1}}$, $r_{n+1}=(1-s_{n+1}^3)^{1/3}$
3, Calculate $a_k$ which converges nonically to $1/\pi$
import math
a = 1/3
r = (math.sqrt(3)-1)/2
s = (1-r**3)**(1/3)
t = 0
u = 0
v = 0
w = 0
pi = 0
for i in range(1, 10):
olda, oldr, olds, oldt, oldu, oldv, oldw, oldpi = a, r, s, t, u, v, w, pi
t = (1+2*oldr)
u = (9*oldr*(1+oldr+oldr**2))**(1/3)
v = t**2+t*u+u**2
w = 27*(1+olds+olds**2)/v
a = w*olda+3**(2*i-3)*(1-w)
s = (1-oldr)**3/(t+2*u)/v
r = (1-s**3)**(1/3)
pi = 1/a
print ("at cycle ", i, " increase=", format(pi-oldpi, ".20e"), " pi=", format(pi, ".20f"))
at cycle 1 increase= 2.99999999999999866773e+00 pi= 2.99999999999999866773 at cycle 2 increase= 1.41592653589794448266e-01 pi= 3.14159265358979311600 at cycle 3 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 4 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 5 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 6 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 7 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 8 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600 at cycle 9 increase= 0.00000000000000000000e+00 pi= 3.14159265358979311600
Again, we do the same calculation using decimal module and see what happens. You can change the prec value yourself to see how results change based on precision. The convergence is very fast as you will see below.
import math
getcontext().prec = 200 #set 100 digits precision
a = Decimal(1)/Decimal(3)
r = (Decimal(3).sqrt()-Decimal(1))/Decimal(2)
s = (Decimal(1)-r**3)**(Decimal(1)/Decimal(3))
t = Decimal(0)
u = Decimal(0)
v = Decimal(0)
w = Decimal(0)
pi = Decimal(0)
for i in range(1, 10):
olda, oldr, olds, oldt, oldu, oldv, oldw, oldpi = a, r, s, t, u, v, w, pi
t = (Decimal(1)+Decimal(2)*oldr)
u = (Decimal(9)*oldr*(1+oldr+oldr**2))**(Decimal(1)/Decimal(3))
v = t**2+t*u+u**2
w = Decimal(27)*(Decimal(1)+olds+olds**2)/v
a = w*olda+Decimal(3)**Decimal(2*i-3)*(Decimal(1)-w)
s = (Decimal(1)-oldr)**3/(t+Decimal(2)*u)/v
r = (Decimal(1)-s**3)**(Decimal(1)/Decimal(3))
pi = Decimal(1)/a
print ("at cycle ", i, " increase=", format(pi-oldpi, ".20e"), " pi=", format(pi, ".20f"))
pi
at cycle 1 increase= 3.00000000000000000000e+0 pi= 3.00000000000000000000 at cycle 2 increase= 1.41592653589793238462e-1 pi= 3.14159265358979323846 at cycle 3 increase= 2.18202622963951475564e-22 pi= 3.14159265358979323846 at cycle 4 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846 at cycle 5 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846 at cycle 6 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846 at cycle 7 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846 at cycle 8 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846 at cycle 9 increase= 0.00000000000000000000e-179 pi= 3.14159265358979323846
Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303697')
In conclusion, this Borwein algorithm is perhaps among the best ways to calculate $\pi$ values.
I hope you enjoy the Pi Day!