I am writing a Python program to compute the number π to as many decimal places as possible. My first difficulty was that I used variables float
that restricted the number of decimal places.
Thanks to forum suggestions, I switched to decimal
.
The fact is that the decimal approximation improved but only up to the 16th decimal, how it can be seen as follows:
π with my program
3.1415926535897931
667462219700150778696165489637681660703093062077
π with decimal value (math.pi)
3.1415926535897931
15997963468544185161590576171875
Here is the source code of my first program:
import math
from decimal import Decimal
import decimal
decimal.getcontext().prec = 65
a = Decimal(0)
valorN = 0
i = 3
pi = Decimal(0)
pi2 = Decimal(math.pi)
valorN = int(input('Ingrese el valor de n que sea mayor a 2 y menor a infinito: '))
a = decimal.Decimal(0)
while i <= valorN:
x1= decimal.Decimal(Decimal(2)+a).sqrt()
x = decimal.Decimal(Decimal(2)-x1).sqrt()
y = decimal.Decimal(pow(2,i-1))
pi = x*y
a = decimal.Decimal(Decimal(2)+a).sqrt()
i = i + 1
print('El valor de pi es ')
print(pi)
# este es el valor real #
print (pi2)
TL;DR
Your supposed "exact value of pi" that you were comparing against was not exact. It was only correct up to decimal 15. In fact, the value of pi that your loop computed was more correct (and therefore departed from the value of
math.pi
after decimal 15).However, the algorithm you use to compute pi has serious stability problems from a point. [See final extension for a possible solution]
Understand Decimal
First of all, the type
Decimal()
is not a miracle formula. When you convert afloat
toDecimal
you're not going to instantly get more precision than thefloat
input had. For example, if you ask him to represent the quantity 3.14 with 51 decimal places, you will not get more decimals of pi, moreover, you will not even get 3.1400000...00000 (49 zeros) butAs you can see, "rare" decimals appear after a point. Those decimals were already there in number 3.14 because its binary representation has infinite decimals, and since saving it to
float
that truncates, you're making a mistake. In other words, when you doa = 3.14
(type float), the number that is actually stored ina
is an approximation of 3.14 (and specifically, the approximation shown above).To avoid these rounding errors you should not pass a
float
to the constructor ofDecimal()
, but a string. If we doDecimal("3.14")
, then yes, the stored value will be exact:Although it doesn't show non-significant zeros at print time, they are actually correctly stored within the Decimal representation.
I say all this because you start with:
and you consider this variable to be "the exact value of pi", but it is not since it has been generated from the fact
math.pi
that it is an approximation valid only up to decimal 15, since it is a floating point number that has inherent errors as already explained hereCompare the value of
pi2
to a more exact value generated by Wolfram Alpha to decimal 65:Some additional details about your program
In your program you have several
decimal.Decimal()
that could be eliminated.For example left over here:
which can be shortened to:
since it
a
was already of typeDecimal
, so ita+2
will also beDecimal
(python will convert2
toDecimal
, exactly because it is an integer, and do the addition). Since the result is aDecimal()
and we apply the method.sqrt()
of theDecimal()
, the result of that root will in turn be anotherDecimal()
that we can assign tox1
directly, without having to convert it back toDecimal()
Running your corrected program
We are going to execute a simplified version (removing the
decimal.Decimal()
superfluous) in which we compare ourselves with the "true" value of pi, at least up to the number 65, which is what we have obtained from Wolfram AlphaThis is the program:
When we run it for N=28 we get correct results up to decimal 15
But what did you expect? In 28 iterations of the algorithm that is the precision that has been achieved. If you want more precision, we can keep iterating. Let's try for example 35 iterations:
As we can see, the precision has improved so that we now have 19 correct decimal places (which is already more than we could have in a
float
).It happens that N iterations does not give you N correct decimal places. The algorithm does not converge that fast. To achieve 19 decimal places you must iterate 35 times. But from a point on, continuing to iterate no longer improves the result. In fact it makes it worse!
The problem with this approach
This is not a good mechanism for finding decimals of pi, as it is unstable. As you iterate, it
x1
's getting closer to 2, so it2-x1
's getting closer to zero, and sox
too.In fact, there comes a time when the 65 decimal places are not enough to save
x
(which would be less than 10^-65) and its value is truncated to zero. From that point everything fails, because if itx
is zero, it will also be zerox**y
and therefore suddenly your approximation of pi becomes zero.This happens relatively soon (for n=110). But well before that happens, important errors appear in the computation of
x**y
, since itx
will take very small values, while ity
will take very large values (powers of 2). On small values ofx
there is an error because we are truncating it at its decimal 65, and that error is amplified by raising it toy
. The result is that before the "catastrophe" occurs that causes it topi
go to zero, the previous approximations were getting worse and worse instead of being better and better.For example, for
n=105
the approximationpi
that came out was:Take! It is wrong already from the third decimal!
It seems then that as we increase N we get closer to pi, but only up to a point from which we move away again due to the errors caused because it
x
is very very small and does not fit in 65 decimal places, until finally the error is already catastrophic because our pi is zero.final curiosities
As a curiosity I have made a graph of the absolute error made (difference between the approximation of pi and its "true" value) as we increase N (loop iterations):
I have used logarithmic scale on the Y axis. We see how up to N approximately equal to 53 things went well. The error was reducing exponentially, but from there things went wrong. Rounding errors due to instability
x**y
caused the error to grow again (irregularly, somewhat chaotically), until reaching an error equal to 3.1415... whenpi
it becomes 0 (and from that point the error it already stays at 3.1415.. because every subsequent iteration of the loop producespi = 0
.For N=53, the best approximation of all is produced, which is the following:
Correct up to decimal 30.
I have painted another graph that shows how many correct digits the approximation has as you increase
valorN
. Is the next:Where it is seen again that the maximum is reached for N around 55 and also that up to that moment the progression is not linear. Sometimes going from N to N+1 increases the number of correct digits by 1, other times it doesn't. And from 55 the number of correct digits decreases, also "in jumps".
It therefore seems that with a precision of 65 you get correctly up to decimal 30. By increasing the precision you could get more correct decimals.
Extension
Apparently, based on some tests I've been doing, to get N digits correct, you have to work with a precision of at least 2*(N+3) (it's a heuristic I have no justification for). So, to correctly get 65 digits you should work with a precision of 136.
Instead of asking the user for the number of iterations to perform, we can automatically detect when to stop, and it will be when the value that comes out of
pi
is equal to that of the previous iteration (from that point on the approximation would start to get worse).So this would be the code:
Execution result:
Naturally the rest of the decimals that come out from position 65 would be wrong. We could truncate the result with: