The proposed solutions using np.pi, math.pi, etc only only work to double precision (~14 digits), to get higher precision you need to use multi-precision, for example the mpmath package

>>> from mpmath import mp
>>> mp.dps = 20    # set number of digits
>>> print(mp.pi)
3.1415926535897932385

Using np.pi gives the wrong result

>>> format(np.pi, '.20f')
3.14159265358979311600

Compare to the true value:

3.14159265358979323846264338327...
Answer from Jonas Adler on Stack Overflow
๐ŸŒ
Python
docs.python.org โ€บ 3 โ€บ library โ€บ decimal.html
decimal โ€” Decimal fixed-point and floating-point arithmetic
The philosophy of the decimal specification is that numbers are considered exact and are created independent of the current context. They can even have greater precision than current context. Computations process with those exact inputs and then rounding (or other context operations) is applied to the result of the computation: >>> getcontext().prec = 5 >>> pi = Decimal('3.1415926535') # More than 5 digits >>> pi # All digits are retained Decimal('3.1415926535') >>> pi + 0 # Rounded after an addition Decimal('3.1416') >>> pi - Decimal('0.00005') # Subtract unrounded numbers, then round Decimal('3.1415') >>> pi + 0 - Decimal('0.00005').
Top answer
1 of 3
52

That is the Taylor series of at (times 6).

2 of 3
47

This approximation for is attributed to Issac Newton:

  • https://loresayer.com/2016/03/14/pi-infinite-sum-approximation/
  • http://www.geom.uiuc.edu/~huberty/math5337/groupe/expresspi.html
  • http://www.pi314.net/eng/newton.php

When I wrote that code shown in the Python docs, I got the formula came from p.53 in "The Joy of ฯ€". Of the many formulas listed, it was the first that:

  1. converged quickly,
  2. was short,
  3. was something I understood well-enough to derive by hand, and
  4. could be implemented using cheap operations: several additions with only a single multiply and single divide for each term. This allowed the estimate of to be easily be written as an efficient function using Python's floats, or with the decimal module, or with Python's multi-precision integers.

The formula solves for ฯ€ in the equation .

WolframAlpha gives the Maclaurin series for as:

Evaluating the series at gives:

From there, I used finite differences, to incrementally compute the numerators and denominators. The numerator differences were 8, 16, 24, ..., hence the numerator adjustment na+8 in the code. The denominator differences were 56, 88, 120, ..., hence the denominator adjustment da+32 in the code:

 1     9    25    49    numerators
    8    16    24       1st differences
       8     8          2nd differences


24    80   168   288    denominator 
   56    88   120       1st differences
      32    32          2nd differences

Here is the original code I wrote back in 1999 using Python's multi-precision integers (this predates the decimal module):

def pi(places=10):
    "Computes pi to given number of decimal places"
    # From p.53 in "The Joy of Pi".  sin(pi/6) = 1/2
    # 3 + 3*(1/24) + 3*(1/24)*(9/80) + 3*(1/24)*(9/80)*(25/168)
    # The numerators 1, 9, 25, ... are given by  (2x + 1) ^ 2
    # The denominators 24, 80, 168 are given by 16x^2 +40x + 24
    extra = 8
    one = 10 ** (places+extra)
    t, c, n, na, d, da = 3*one, 3*one, 1, 0, 0, 24
    while t > 1:
        n, na, d, da  = n+na, na+8, d+da, da+32
        t = t * n // d
        c += t
    return c // (10 ** extra)
๐ŸŒ
W3Resource
w3resource.com โ€บ projects โ€บ python โ€บ python-projects-1.php
Python Projects: Compute the value of Pi to n number of decimal places - w3resource
#https://github.com/rlingineni/PythonPractice/blob/master/piCalc/pi.py import math def CalculatePi(roundVal): somepi = round(math.pi,roundVal); pi = str(somepi) someList = list(pi) return somepi; roundTo = input('Enter the number of digits you want after the decimal for Pi: ') try: roundint = int(roundTo); print(CalculatePi(roundint)); except: print("You did not enter an integer");
๐ŸŒ
GitConnected
levelup.gitconnected.com โ€บ generating-the-value-of-pi-to-a-known-number-of-decimals-places-in-python-e93986bb474d
Generating the value of Pi to a known number of decimals places in python
November 11, 2022 - This article will explain how to write an algorithm that will generate the values of ฯ€ to a specified number of decimals using the Chudnovsky algorithm in python.
๐ŸŒ
Replit
replit.com โ€บ home โ€บ discover โ€บ how to use pi in python
How to use pi in Python | Replit
... from decimal import Decimal, getcontext getcontext().prec = 50 # Set precision to 50 digits pi = Decimal('3.14159265358979323846264338327950288419716939937510') radius = Decimal('10') print(f"Circle circumference (r=10): {2 * pi * radius}")
Find elsewhere
๐ŸŒ
Sololearn
sololearn.com โ€บ en โ€บ Discuss โ€บ 3059178 โ€บ pi-to-more-than-15-digits
PI to more than 15 digits | Sololearn: Learn to code for FREE!
July 12, 2022 - From math import pi Print (pi) Python returns PI number to only 15 digits after decimal point. How can I print it to more digits?
๐ŸŒ
DEV Community
dev.to โ€บ puzzleddev โ€บ calculation-pi-to-million-decimals-in-python-423b
Calculating Pi to million decimals in Python - DEV Community
May 16, 2021 - ...and decided to calculate the pi number. In the python math library, pi is stored only with the first 15 decimals. But as you could see from the title, I wanted to calculate many more characters.
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python โ€บ calculate-pi-with-python
Calculate Pi with Python - GeeksforGeeks
February 25, 2026 - In this example, the Python script uses the acos function from the math module to calculate the value of pi (ฯ€) up to 3 decimal places.
Top answer
1 of 1
5

Thanks @Aemyl for your post. From a quick check of your code, these initial issues jump out at me:

  • Using __name__ entry point as a runner for main() function
  • Global statements (c = getcontext()) outside of __main__
  • sums = [Decimal(0) for i in range(4)] - unused variable i
  • Function is_close naming. Is close to what?
  • Using break (twice) instead of a flag variable explaining why you're ending the loop. in main and in newton

Let's go over them, and then look at some performance tuning.

Entry point as runner

I see this a lot from people who come from C, but __main__ IS your main already. You should place the content of main() into the entry point where it belongs. This is where other programmers will come to understand how your code runs and why it's doing what it's doing. i.e.:

if __name__ == '__main__':
    x = Decimal(1)
    while True:
        tmp = newton(x)
        ....

Global statements

c = getcontext()
c.prec = 1000

Understanding the import system of Python is important if you want to improve your Python skills. In this case, the modification of the Decimal can be done inside __main__, so we now have:

if __name__ == '__main__':
    c = getcontext()
    c.prec = 1000
    x = Decimal(1)
    while True:
        ....

Unused variable i

The statement sums = [Decimal(0) for i in range(4)] doesn't do anything with i. When I look at that line in my debugger, I can see that it's just populating sums with 4 entries of Decimal(0). Let's look at it from the performance checker's view:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    19         8         84.0     10.5      0.0      sums = [Decimal(0) for i in range(4)]

And now let's convert the loop into a static statement and re-run that to see if it's improved performance:

Line #      Hits         Time  Per Hit   % Time  Line Contents
============================================================== 
    21         8         55.0      6.9      0.0      sums = [Decimal(0),Decimal(0),Decimal(0),Decimal(0)]

That's a little better...

Variable naming

When you're writing code, it's not about you "now" - it's about the "future you", as well as - if you do it professionally - the other coders who will look at your code. Think about how many times you've come back to your code after a few weeks or months, and you ask yourself "who wrote this rubbish?" and "what the hell was I thinking?" - I know I've had that experience quite a few times.

Variable naming is an important part of explaining what your code is trying to do.

So, as an example of throwaway variables, one of the things Python allows you to do, is use _ as a variable for these throwaway variables. For example, this code:

for i in count(1):
    value *= x / i
    tmp = sums[i % 4] + value
    if tmp == sums[i % 4]:
        break
    sums[i % 4] = tmp
    

Let's replace all the tmp variables with _:

for i in count(1):
    value *= x / i
    _ = sums[i % 4] + value
    if _ == sums[i % 4]:
        break
    sums[i % 4] = _
    

Looks better right? Actually no, it doesn't. So tmp really isn't a throwaway variable right? It means something. We need to name the variables to explain what they're doing. Perhaps calc_val or quickly looking at some notes about the Newton method, it looks like it's calculating area. So perhaps area is better? If I'm wrong, sorry, it was a very quick look.

Using Break

Looking at the main, we see break being used to escape the loop. This approach results in spaghetti code. You should cleanly exit any loop using a control variable or refactor it into a function with a return statement.

Original code is:

while True:
    tmp = newton(x)
    if is_close(tmp, x):
        x = tmp
        break
    x = tmp
print(x * 2)

Let's introduce a control variable and complete the if into an if/else:

calc_pi = True
while calc_pi:
    tmp = newton(x)
    if is_close(tmp, x):
        x = tmp
        calc_pi = False
    else:
        x = tmp
print(x * 2)
        

Now we're not using break, the loop will exit cleanly into the print statement.

Ah! But what is this? What do we see?

It's now clear that both paths of the if statement perform the same action. Is this an error? Let's make sure the output is the same, then make a change.

result = x * 2
print(result)
if str(result) != "3.14159265....0214":
    print("wrong, didn't match")

And write the changes:

while calc_pi:
    tmp = newton(x)
    if is_close(tmp, x):
        calc_pi = False
    x = tmp

And run the code - yes, the result matches. So this proves the paths were equal and we were correct to refactor that code into that final version (above).

We can also perform a similar change to the newton(x) code, but this is getting a little long, and you asked for performance enhancements.

Performance Checking

Running line_profiler (there's lots of examples how on the 'net), we have the following results:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    19                                           @profile
    20                                           def newton(x):
    21         8         55.0      6.9      0.0      sums = [Decimal(0),Decimal(0),Decimal(0),Decimal(0)]
    22         8         15.0      1.9      0.0      value = Decimal(1)
    23         8         24.0      3.0      0.0      sums[0] += value
    24      3857       3140.0      0.8      1.2      for i in count(1):
    25      3857     233157.0     60.5     86.2          value *= x / i
    26      3857      13665.0      3.5      5.0          tmp = sums[i % 4] + value
    27      3857      15645.0      4.1      5.8          if tmp == sums[i % 4]:
    28         8          9.0      1.1      0.0              break
    29      3849       4438.0      1.2      1.6          sums[i % 4] = tmp
    30         8         21.0      2.6      0.0      cos_x = sums[0] - sums[2]
    31         8         19.0      2.4      0.0      sin_x = sums[1] - sums[3]
    32         8        416.0     52.0      0.2      return x + cos_x / sin_x

Well, value and sums creations still don't look right. Line 23 is doing another static assignment which we can roll into line 21. So, let's change sums[0] into Decimal(1) and remove line 23.

Line 25 seems to be doing most of the work, but I can see that you're continually recalculating modulo. Let's create a variable storing the modulo result and use that instead.

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    14                                           @profile
    15                                           def newton(x):
    16         8         33.0      4.1      0.0      sums = [Decimal(1), Decimal(0), Decimal(0), Decimal(0)]
    17         8          9.0      1.1      0.0      value = Decimal(1)
    18      3857       2314.0      0.6      1.3      for i in count(1):
    19      3857     152870.0     39.6     87.9          value *= x / i
    20      3857       3353.0      0.9      1.9          mod_val = i % 4
    21      3857       8470.0      2.2      4.9          tmp = sums[mod_val] + value
    22      3857       3670.0      1.0      2.1          if tmp == sums[mod_val]:
    23         8          4.0      0.5      0.0              break
    24      3849       2961.0      0.8      1.7          sums[mod_val] = tmp
    25         8         13.0      1.6      0.0      cos_x = sums[0] - sums[2]
    26         8         11.0      1.4      0.0      sin_x = sums[1] - sums[3]
    27         8        274.0     34.2      0.2      return x + cos_x / sin_x

So changes thus far have made it run about 60% better (270604 -> 173982). And changing the for loop into a while loop with a control variable:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    14                                           @profile
    15                                           def newton(x):
    16         8         31.0      3.9      0.0      sums = [Decimal(1), Decimal(0), Decimal(0), Decimal(0)]
    17         8          9.0      1.1      0.0      value = Decimal(1)
    18         8          6.0      0.8      0.0      do_calcs = True
    19         8          3.0      0.4      0.0      counter = 1
    20      3865       2430.0      0.6      1.2      while do_calcs:
    21      3857     173534.0     45.0     87.4          value *= x / counter
    22      3857       3466.0      0.9      1.7          mod_val = counter % 4
    23      3857       8579.0      2.2      4.3          tmp = sums[mod_val] + value
    24      3857       3994.0      1.0      2.0          if tmp == sums[mod_val]:
    25         8          6.0      0.8      0.0              do_calcs = False
    26                                                   else:
    27      3849       3214.0      0.8      1.6              sums[mod_val] = tmp
    28      3849       2973.0      0.8      1.5              counter += 1
    29         8         16.0      2.0      0.0      cos_x = sums[0] - sums[2]
    30         8         13.0      1.6      0.0      sin_x = sums[1] - sums[3]
    31         8        283.0     35.4      0.1      return x + cos_x / sin_x

But as you can see, that slowed it down a little (198274; slower by 24292) although the code is much clearer, and we can now refactor that into a function - because the break has been removed (I use PyCharm's Refactor->Extract->Method approach).

Also, we unwrap the *= into a full calculation, and reintroduce your original for loop via count(1), primarily because we've wrapped it into a function so it can exit cleanly with a return statement:

Total time: 0.191908 s
File: 0908_calc_pi.py
Function: newton at line 14

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    14                                           @profile
    15                                           def newton(x):
    16         8         35.0      4.4      0.0      sums = [Decimal(1), Decimal(0), Decimal(0), Decimal(0)]
    17         8          9.0      1.1      0.0      value = Decimal(1)
    18         8     191565.0  23945.6     99.8      sums = perform_calcs(sums, value, x)
    19         8         16.0      2.0      0.0      cos_x = sums[0] - sums[2]
    20         8         11.0      1.4      0.0      sin_x = sums[1] - sums[3]
    21         8        272.0     34.0      0.1      return x + cos_x / sin_x

Total time: 0.18069 s
File: 0908_calc_pi.py
Function: perform_calcs at line 23

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    23                                           @profile
    24                                           def perform_calcs(sums, value, x):
    25      3857       2152.0      0.6      1.2      for counter in count(1):
    26      3857     162137.0     42.0     89.7          value = value * (x / counter)
    27      3857       2765.0      0.7      1.5          mod_val = counter % 4
    28      3857       7591.0      2.0      4.2          tmp = sums[mod_val] + value
    29      3857       3497.0      0.9      1.9          if tmp == sums[mod_val]:
    30         8          8.0      1.0      0.0              return sums
    31                                                   else:
    32      3849       2540.0      0.7      1.4              sums[mod_val] = tmp

Giving us a value of 191908. That's a little slower than the best, but it's cleaner and refactored (variable naming needs some work still). That's about the best I can think of off the top of my head. I hope this helps!

๐ŸŒ
Medium
jccraig.medium.com โ€บ baking-1000-digits-of-pi-from-3-small-lines-of-python-579da9c3bc49
Baking 1000 Digits of Pi from 3 Small Lines of Python | by John Clark Craig | Medium
July 31, 2023 - Baking 1000 Digits of Pi from 3 Small Lines of Python Pythonโ€™s mpmath module is a powerful tool for high accuracy calculation that you should know You likely know that integers in Python can grow โ€ฆ
๐ŸŒ
Python Module of the Week
pymotw.com โ€บ 2 โ€บ decimal
decimal โ€“ Fixed and floating point math - Python Module of the Week
import decimal # Set up a context with limited precision c = decimal.getcontext().copy() c.prec = 3 # Create our constant pi = c.create_decimal('3.1415') # The constant value is rounded off print 'PI:', pi # The result of using the constant uses the global context print 'RESULT:', decimal.Decimal('2.01') * pi ยท $ python decimal_instance_context.py PI: 3.14 RESULT: 6.3114
๐ŸŒ
Vicente Hernando
vhernando.github.io โ€บ calculate-pi-digits-python-high-precision
Calculate PI digits (arbitrary-precision) using Python - Vicente Hernando
#!/usr/bin/python3 # Calculate PI number using this formula: # x[n+1] = x[n] + sin(x[n]) # # x[n+1] = x[n] + x[n] + x[n]**3/fact(3) - x[n]**5/fact(5) + x[n]**7/fact(7) - x[n]**9/fact(9) + .... from decimal import getcontext, Decimal import sys if len(sys.argv) < 2: print('Not enough arguments') ...
๐ŸŒ
Guidetopython
en.guidetopython.com โ€บ home โ€บ index โ€บ decimal
Python decimal - Stdlib โ€” Math | Guide to Python
python ยท from decimal import Decimal, getcontext # Precise decimal arithmetic print(0.1 + 0.2) # Float imprecision print(Decimal("0.1") + Decimal("0.2")) # Exact getcontext().prec = 50 pi = Decimal("3.14159265358979323846264338327950288419716939937510") print(f"Pi to 50 digits: {pi}") The ...
๐ŸŒ
Mostly Python
mostlypython.com โ€บ how-many-digits-are-there-in-pi
How many digits are there in pi?
March 19, 2026 - After running this program, you get pi to 30 decimal places: $ python pi_string.py 3.141592653589793238462643383279 32
๐ŸŒ
Medium
medium.com โ€บ @HeCanThink โ€บ pi-exploring-the-digits-of-pi-using-python-224d20937185
Pi: Exploring the Digits of Pi Using Python ๐Ÿฅง | by Manoj Das | Medium
March 16, 2023 - and the decimal representation of pi goes on infinitely without repeating. Pi is used in various branches of mathematics, science, and engineering, particularly in calculations related to circles, spheres, and trigonometry.
๐ŸŒ
Medium
medium.com โ€บ @cosinekitty โ€บ how-to-calculate-a-million-digits-of-pi-d62ce3db8f58
How to Calculate a Million Digits of Pi | by Don Cross | Medium
December 28, 2019 - How computers calculate pi to a million decimal places. Includes Python source code and the math behind it.