Академический Документы
Профессиональный Документы
Культура Документы
In Part 3 we managed to calculate 1,000,000 decimal places of with Machin's arctan formula. Our stated aim was 100,000,000 places which we are going to
achieve now!
This is a having fun with maths and python article. See the introduction for important information!
We have still got a long way to go though, and we'll have to improve both our algorithm (formula) for and our implementation.
The current darling of the world is the Chudnovsky algorithm which is similar to the arctan formula but it converges much quicker. It is also rather complicated.
The formula itself is derived from one by Ramanjuan who's work was extraordinary in the extreme. It isn't trivial to prove, so I won't! Here is Chudnovsky's formula
for as it is usually stated:
That is quite a complicated formula, we will make more comprehensible in a moment. If you haven't seen the notation before it just like a sum over a for loop in
python. For example:
Now let's split it into two independent parts. See how there is a + sign inside the ? We can split it into two series which we will call a and b and work out what is
in terms of a and b:
Finally note that we can calculate the next a term from the previous one, and the b terms from the a terms which simplies the calculations rather a lot.
OK that was a lot of maths! But it is now in an easy to compute state, which we can do like this:
def pi_chudnovsky(one=1000000):
"""
Calculate pi using Chudnovsky's series
This calculates it in fixed point, using the value for one passed in
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 1/9
06/03/2017 Pi - Chudnovsky
"""
k = 1
a_k = one
a_sum = one
b_sum = 0
C = 640320
C3_OVER_24 = C**3 // 24
while 1:
a_k *= -(6*k-5)*(2*k-1)*(6*k-1)
a_k //= k*k*k*C3_OVER_24
a_sum += a_k
b_sum += k * a_k
k += 1
if a_k == 0:
break
total = 13591409*a_sum + 545140134*b_sum
pi = (426880*sqrt(10005*one, one)*one) // total
return pi
We need to be able to take the square root of long integers which python doesn't have a built in for. Luckily this is easy to provide. This uses a square root algorithm
devised by Newton which doubles the number of signicant places in the answer (quadratic convergence) each iteration:
def sqrt(n, one):
"""
Return the square root of n as a fixed point number with the one
passed in. It uses a second order Newton-Raphson convergence. This
doubles the number of significant figures on each iteration.
"""
# Use floating point arithmetic to make an initial guess
floating_point_precision = 10**16
n_float = float((n * floating_point_precision) // one) / floating_point_precision
x = (int(floating_point_precision * math.sqrt(n_float)) * one) // floating_point_precision
n_one = n * one
while 1:
x_old = x
x = (x + n_one // x) // 2
if x == x_old:
break
return x
It uses normal oating point arithmetic to make an initial guess then renes it using Newton's method.
See pi_chudnovsky.py for the complete program. When I run it I get this:
Which is nearly 3 times quicker than the best result in part 3, and gets us 1,000,000 places of in about 15 minutes.
Amazingly there are still two major improvements we can make to this. The rst is to recast the calculation using something called binary splitting. Binary splitting is
a general purpose technique for speeding up this sort of calculation. What it does is convert the sum of the individual fractions into one giant fraction. This means
that you only do one divide at the end of the calculation which speeds things up greatly, because division is slow compared to multiplication.
This could be used as a model for all the innite series we've looked at so far. Now lets consider the partial sum of that series from terms a to b (including a, not
including b).
This is a part of the innite series, and if a=0 and b= it becomes the innite series above.
Let's dene m which is a <= m <= b. Making m as near to the middle of a and b will lead to the quickest calculations, but for the proof, it has to be somewhere
between them. Lets work out what happens to our variables P, Q, R and T when we split the series in two:
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 2/9
06/03/2017 Pi - Chudnovsky
The rst three of those statements are obvious from the denitions, but the last deserves proof.
We can use these relations to expand the series recursively, so if we want S(0,8) then we can work out S(0,4) and S(4,8) and combine them. Likewise to calculate
S(0,4) and S(4,8) we work out S(0,2), S(2,4), S(4,6), S(6,8) and combine them, and to work out those we work out S(0,1), S(1,2), S(2,3), S(3,4), S(4,5), S(5,6),
S(6,7), S(7,8). Luckily we don't have to split them down any more as we know what P(a,a+1), Q(a,a+1) etc is from the denition above.
And when you've nally worked out P(0,n),Q(0,n),B(0,n),T(0,n) you can work out S(0,n) with
If you want a more detailed and precise treatment of binary splitting then see Bruno Haible and Thomas Papanikolaou's paper.
So back to Chudnovksy's series. We can now set these parameters in the above general formula:
def pi_chudnovsky_bs(digits):
"""
Compute int(pi * 10**digits)
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 3/9
06/03/2017 Pi - Chudnovsky
Hopefully you'll see how the maths above relates to this as I've used the same notation in each. Note that we calculate the number of digits of pi we expect per term
of the series (about 14.18) to work out how many terms of the series we need to compute, as the binary splitting algorithm needs to know in advance how many terms
to calculate. We also don't bother calculating B(a) as it is always 1. Dening a function inside a function like this makes what is known as a closure. This means that
the inner function can access the variables in the outer function which is very convenient in this recursive algorithm as it stops us having to pass the constants to
every call of the function.
See pi_chudnovsky_bs.py for the complete program. When I run it I get this:
This is a bit more than twice as fast as pi_chudnovsky.py giving us our 1,000,000 places in just under 7 minutes. If you prole it you'll discover that almost all the
time spent in the square root calculations (86% of the time) whereas only 56 seconds is spent in the binary splitting part. We could spend time improving the square
root algorithm, but it is time to bring out the big guns: gmpy.
Gmpy is a python interface to the gmp library which is a C library for arbitrary precision arithmetic. It is very fast, much faster than the built in int type in python
for large numbers. Luckily gmpy provides a type (mpz) which works exactly like normal python int types, so we have to make hardly any changes to our code to use
it. These are using initialising variables with the mpz type, and using the sqrt method on mpz rather than our own home made sqrt algorithm:
import math
from gmpy2 import mpz
from time import time
def pi_chudnovsky_bs(digits):
"""
Compute int(pi * 10**digits)
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 4/9
06/03/2017 Pi - Chudnovsky
Pam, Qam, Tam = bs(a, m)
# Recursively calculate P(m,b), Q(m,b) and T(m,b)
Pmb, Qmb, Tmb = bs(m, b)
# Now combine
Pab = Pam * Pmb
Qab = Qam * Qmb
Tab = Qmb * Tam + Pam * Tmb
return Pab, Qab, Tab
# how many terms to compute
DIGITS_PER_TERM = math.log10(C3_OVER_24/6/2/6)
N = int(digits/DIGITS_PER_TERM + 1)
# Calclate P(0,N) and Q(0,N)
P, Q, T = bs(0, N)
one_squared = mpz(10)**(2*digits)
sqrtC = (10005*one_squared).sqrt()
return (Q*426880*sqrtC) // T
See pi_chudnovsky_bs_gmpy.py for the complete program. When I run it I get this:
So we have achieved our goal of calculating 100,000,000 places of in just under 10 minutes! What is limiting the program now is memory... 100,000,000 places
takes about 600MB of memory to run. With 6 GB of free memory it could probably calculate one billion places in a few hours.
What if we wanted to go faster? Well you could use gmp-chudnovsky.c which is a C program which implements the Chudnovsky Algorithm. It is heavily optimised
and rather difcult to understand, but if you untangle it you'll see it does exactly the same as above, with one extra twist. The twist is that it factors the fraction in the
binary splitting phase as it goes along. If you examine the gmp-chudnovsky.c results page you'll see that the little python program above acquits itself very well - the
python program only takes 75% longer than the optimised C program on the same hardware.
One algorithm which has the potential to beat Chudnovsky is the Arithmetic Geometric Mean algorithm which doubles the number of decimal places each iteration.
It involves square roots and full precision divisions which makes it tricky to implement well. In theory it should be faster than Chudnovsk but, so far, in practice
Chudnovsky is faster.
Here is a chart of all the different programs we've developed in Part 1, Part 2, Part 3 and Part 4 with their timings on my 2010 Intel Core i7 CPU Q820 @
1.73GHz running 64 bit Linux:
Sort by Best
Recommend 1 Share
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 5/9
06/03/2017 Pi - Chudnovsky
changing "sqrtC = (10005*one_squared).sqrt()" to "sqrtC = isqrt(10005*one_squared)" and adding "from gmpy2 import isqrt" will allow the script to
run correctly.
Reply Share
Stop the iteration when current term becomes less than 0.0000001 and then report (print) calculated value.
Reply Share
A. Install GNU MP
==============
1. Decompress the referenced "gmp library" (download from http://gmplib.org) into a user folder (E.g. /home/texadactyl/gmplib).
2. Inside the top level folder:
./configure
make
make check
sudo make install
Be sure that no errors have occured on any of the steps before proceeding to the next step.
B. Build gmp-chudnovsky
====================
1. Go into the folder where all the source files extracted from this web page reside.
2. For an intel i3, i5, or i7, this will compile and link an efficient executable for gmp-chudnovsky.c:
Note that the i3/i5/i7 parameters optimizes for that particular platform. How did I figure the tuning gcc parameters out? I watched the compile for
tests/t-bswap.c which is also a user-level main program.
Reply Share
These are very nice articles for Pi lovers. I'm fascinated with Pi and although I still can't make my head around Gregory's series for arctan, I have
orgasms while my mid-2010 MacBook Pro calculates millions of places of Pi in seconds. Even though I won't make any practical use of these algorithms
since a few places of Pi is enough for everyday work, maybe I can learn from the technics you show and apply them in other projects.
And just because I'm so in love with Pi, I tried every little Python program from these series and all worked except the last one. The gmpy2 optimized
version.
I changed the line to: sqrtC = gmpy2.isqrt(10005*one_squared), and got it working, but this was pure luck since gmpy2.sqrt() gives wrong digits of Pi
and math.sqrt can't cast from mpz to float.
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 6/9
06/03/2017 Pi - Chudnovsky
and math.sqrt can't cast from mpz to float.
Norberto
Reply Share
digitprint = str(pi)
str(pi)
f = open("filename.txt", 'w')
f.write(digitprint)
f.close()
print("We have printed to the textfile.")
::::::::::::::::::::
To the part of the script that prints the answer to the console, You can change the variable if you want.
Reply Share
Could you rerun that with PyPy 1.6? It's numerical prowess is well-regarded.
Reply Share
Powered by Blogole.
Home
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 8/9
06/03/2017 Pi - Chudnovsky
Articles
Latest Articles
Categories
BooksfromMyLibrary
GridlinkedbyNealAsher
AgainstaDarkBackgroundbyIainM.Banks
GSMNetworks:Protocols,TerminologyandImplementation(ArtechHouseMobileCommunicationsLibrary.)byGunnarHeine
HTML&XHTML:TheDefinitiveGuidebyChuckMusciano
PoweredbyLibraryThing
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 9/9