Вы находитесь на странице: 1из 9

06/03/2017 Pi - Chudnovsky

Pi - Chudnovsky Like 9 Tweet 27 2011-10-08

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!

Fun with Maths and Python

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:

Is exactly the same as this python fragment:


sum(k**2 for k in range(1,11))

First let's get rid of that funny fractional power:

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:

Digits Time (seconds)


10 4.696e-05
100 0.0001530
1000 0.002027
10000 0.08685
100000 8.453
1000000 956.3

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.

Consider the general innite series

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.

Now lets dene some extra functions:

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:

This then makes our Chudnovksy pi function look like this:

def pi_chudnovsky_bs(digits):
"""
Compute int(pi * 10**digits)

This is done using Chudnovsky's series with binary splitting


"""
C = 640320
C3_OVER_24 = C**3 // 24
def bs(a, b):
"""
Computes the terms for binary splitting the Chudnovsky infinite series

a(a) = +/- (13591409 + 545140134*a)


p(a) = (6*a-5)*(2*a-1)*(6*a-1)
b(a) = 1
q(a) = a*a*a*C3_OVER_24

http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 3/9
06/03/2017 Pi - Chudnovsky

returns P(a,b), Q(a,b) and T(a,b)


"""
if b - a == 1:
# Directly compute P(a,a+1), Q(a,a+1) and T(a,a+1)
if a == 0:
Pab = Qab = 1
else:
Pab = (6*a-5)*(2*a-1)*(6*a-1)
Qab = a*a*a*C3_OVER_24
Tab = Pab * (13591409 + 545140134*a) # a(a) * p(a)
if a & 1:
Tab = -Tab
else:
# Recursively compute P(a,b), Q(a,b) and T(a,b)
# m is the midpoint of a and b
m = (a + b) // 2
# Recursively calculate P(a,m), Q(a,m) and T(a,m)
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 = 10**digits
sqrtC = sqrt(10005*one, one)
return (Q*426880*sqrtC) // T

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:

Digits Time (seconds)


10 1.096e-05
100 3.194e-05
1000 0.0004899
10000 0.03403
100000 3.625
1000000 419.1

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)

This is done using Chudnovsky's series with binary splitting


"""
C = 640320
C3_OVER_24 = C**3 // 24
def bs(a, b):
"""
Computes the terms for binary splitting the Chudnovsky infinite series

a(a) = +/- (13591409 + 545140134*a)


p(a) = (6*a-5)*(2*a-1)*(6*a-1)
b(a) = 1
q(a) = a*a*a*C3_OVER_24

returns P(a,b), Q(a,b) and T(a,b)


"""
if b - a == 1:
# Directly compute P(a,a+1), Q(a,a+1) and T(a,a+1)
if a == 0:
Pab = Qab = mpz(1)
else:
Pab = mpz((6*a-5)*(2*a-1)*(6*a-1))
Qab = mpz(a*a*a*C3_OVER_24)
Tab = Pab * (13591409 + 545140134*a) # a(a) * p(a)
if a & 1:
Tab = -Tab
else:
# Recursively compute P(a,b), Q(a,b) and T(a,b)
# m is the midpoint of a and b
m = (a + b) // 2
# Recursively calculate P(a,m), Q(a,m) and T(a,m)

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:

Digits Time (seconds)


10 1.597e-05
100 3.409e-05
1000 0.003403
10000 0.003571
100000 0.09120
1000000 1.760
10000000 30.11
100000000 542.2

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:

Categories: maths, pymath, python | 29 Comments


29 Comments Nick Craig-Wood's Pages
1 Login

Sort by Best
Recommend 1 Share

Join the discussion

William Dover 8 months ago

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

Ivo Gelov a year ago


I have doubts that there is a mistake in the second program (pi_chudnovsky_bs.py) - it should be "one = 10**(2*digits)" as in the last program. If you
do not double the digits - then for odd number of digits function returns wrong result (starting with "99....")
Reply Share

Salim Mahdum a year ago


I HAVE A PROBLEM LIKE THIS CAN YOU HELP ME ?

Calculate 's value by using the following series:

= 3 + 4/(2*3*4) - 4/(4*5*6) + 4/(6*7*8) - 4/(8*9*10) + 4/(10*11*12) - 4/(12*13*14) ...

Stop the iteration when current term becomes less than 0.0000001 and then report (print) calculated value.
Reply Share

naraphim a year ago


Can someone please show all the steps to arrive at the final form of a_k/a_k-1
\begin{eqnarry*}
\frac{a_{a_{k}}{a_{k-1}}
\end{eqnarry*}
thank you
Reply Share

naraphim > naraphim a year ago


I found out how this value is calculated. For details and extra insite into calculating Pi please visit my blog at http://moduscalculi.blogspo...
Reply Share

texadactyl 2 years ago


Suggestion to Nick: The next time you publish this technote with the gmp-chudnovsky.c source link, please explain how one should efficiently compile
and link this program. What follows should be valid universally for Linux (works fine in the current Xubuntu). Adaptable for Mac (Unix-based) using
tools from either the http://www.macports.org/ project or the http://www.finkproject.org/ project.

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:

gcc -std=gnu99 -O2 -pedantic -fomit-frame-pointer -m64 -mtune=corei7 -march=corei7 \


-o gmp-chudnovsky gmp-chudnovsky.c -L/usr/local/lib/ -lgmp -lm

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

Norberto 3 years ago


Hello!

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.

Is the following line correct? sqrtC = (10005*one_squared).sqrt()

I get: AttributeError: 'mpz' object has no attribute 'sqrt'

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.

BTW, I'm using Python 3.3.5 and gmpy2 2.0.3.

Thanks and best regards,

Norberto
Reply Share

Nick Craig-Wood Mod > Norberto 3 years ago


It looks like the interface to gmpy2 has changed slightly :-( You've made the correct fix according to my investigations. I'll update this page in
due course with the fix.

Thank you very much for bringing that to my attention.

PS glad you enjoyed the series - I like to see pi enthusiasm :-)


Reply Share

Kamal Osman 3 years ago


Hello, Im a new programmer and was trying to implement your code in java and i ran in to a problem, could you please explain what the line: if a & 1:
Tab = -Tab does, thank you
Reply Share

Nick Craig-Wood Mod > Kamal Osman 3 years ago


`a & 1` means if the least significant bit is set, equivalent to `a % 2 == 1`. So in English, if the least significant bit of `a` is set then negate `Tab`.

Hope that helps!


Reply Share

Crazyasianboy436 5 years ago


Could you also use Halley's method to calculate the square root? It has cubic converge but the formula is a little bit more complicated.
Reply Share

Kayleighsinger99 5 years ago


also not to bug you but this is my last question: When calculating pi which algorithm should i use from your links:
Pi - Chudnovsky
Pi - Machin
Pi - Archimedes
Pi - Gregory's Series
Reply Share

Nick Craig-Wood Mod > Kayleighsinger99 5 years ago


The answer would be any of the above! Chudnovsky is the fastest as you can see from the graphs, but you may want analgorithmyou can prove
(like Archimedes) or a very simple one (like Gregory).
Reply Share

Kayleighsinger99 5 years ago


ummm.... im new at this and i am confused. in the early equations that have the python it says k=0 but after that when you have Chudnovsky'sseries it
says k=1. maybe im thinking too much or looking at it wrong. can you help me?
Reply Share

Nick Craig-Wood Mod > Kayleighsinger99 5 years ago


The code starts the iteration at k=1 where the a_sum is 1 and the b_sum is 0, rather than starting with k=0 where the a_sum is 0 and the b_sum
is 0. That just saves a bit of extra complexity in the code and makes it run a tiny bit faster. I didn't explain what I was doing though so you are
right to be confused!
1 Reply Share

Caleb 5 years ago


This is great!! I made some edits so that it prints to a textfile as opposed to just butchering the ram. (: Know i can email my friends the giant file!!!
JIK you want the code all you have to do is add this:::::::::::::::::::

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

Jakob 5 years ago


Great series of articles! I am studying mathematics and taking some computer science courses so this is great fun for me. I have implemented the
Machin method in Java, but my implementation of the Chudnovsky algorithm just won't work. No matter my level of precision (size of one), I get
results like:314159265358998213617, which is wrong. Do you have any idea what could cause it to behave this way? I can upload my code if you have
http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 7/9
06/03/2017 Pi - Chudnovsky
results like:314159265358998213617, which is wrong. Do you have any idea what could cause it to behave this way? I can upload my code if you have
any experience with Java.
Reply Share

Nick Craig-Wood Mod > Jakob 5 years ago


Thanks! And email me the code (email address above) and I'll take a look.
Reply Share

Jakob > Nick Craig-Wood 5 years ago


I sent you an e-mail with the code. Thanks :)
Reply Share

Kai Aeberli 5 years ago


in your term for ak/ak-1 in the denominator: one of the 3k-1 factors should say 3k-2
Reply Share

Nick Craig-Wood Mod > Kai Aeberli 5 years ago


Thanks for that, I've fixed it now - reload images to see it.
Reply Share

Guest 5 years ago


What you used to generate the graph?
Reply Share

Nick Craig-Wood Mod > Guest 5 years ago


I did it with OpenOffice /3.3
Reply Share

Kent Williams-King 5 years ago


The statement: "So we have achieved our goal of calculating 100,000,000 million places of in just under 10 minutes!"is incorrect.

It should be "100,000,000 places", or "100 million places".


Reply Share

Nick Craig-Wood Mod > Kent Williams-King 5 years ago


Oops, will fix in just a moment, thanks!
Reply Share

Sho 5 years ago


> the python
program only takes 75% longer than the optimised C program on the same
hardware

Could you rerun that with PyPy 1.6? It's numerical prowess is well-regarded.
Reply Share

Nick Craig-Wood Mod > Sho 5 years ago


Interesting idea, thanks. I wonder what pypy uses for its long integer maths? I'll definitely try this at some point!
Reply Share

Panos 5 years ago


Great series!!!
Reply Share

Nick Craig-Wood Mod > Panos 5 years ago


Thanks
Reply Share

Subscribe d Add Disqus to your site Add Disqus Add Privacy

Powered by Blogole.

RSS feeds for Entries and Comments.

Home

http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 8/9
06/03/2017 Pi - Chudnovsky
Articles

Latest Articles

Snake Puzzle Solver


IOCCC 2012 Mersenne Prime Checker
How I Solved the GCHQ challenge
Pi - Chudnovsky
Pi - Machin

Categories

Android (rss) (4)


C (rss) (1)
Maths (rss) (6)
Oxo3D (rss) (4)
Pymath (rss) (5)
Python (rss) (7)
Tech (rss) (1)

Nick Craig-Wood 2016 [G+] [Twitter]

Hosted on a Memset Miniserver VM

BooksfromMyLibrary

GridlinkedbyNealAsher

AgainstaDarkBackgroundbyIainM.Banks

GSMNetworks:Protocols,TerminologyandImplementation(ArtechHouseMobileCommunicationsLibrary.)byGunnarHeine

HTML&XHTML:TheDefinitiveGuidebyChuckMusciano

PoweredbyLibraryThing

http://www.craig-wood.com/nick/articles/pi-chudnovsky/ 9/9

Вам также может понравиться