Python对大浮动的计算错误

Python对大浮动的计算错误,python,algorithm,math,floating-point,Python,Algorithm,Math,Floating Point,我的算法返回pi的近似值。它通过使用这个方程来工作 a_2n= math.sqrt(-2*(math.sqrt((n**2)-4*(a_n**2))-n)*n)/2 其中,a_n是n为某个数字时的近似值,a_2n是n为其两倍时的近似值。它从n=4开始,a_n为2,然后应用公式直到n足够高。n越高,计算越精确,只是当n>=2^22时,它突然停止收敛到pi 以下是完整的代码: import math def pi(n): value = 2 round = 4 loop =

我的算法返回pi的近似值。它通过使用这个方程来工作

a_2n= math.sqrt(-2*(math.sqrt((n**2)-4*(a_n**2))-n)*n)/2
其中,a_n是n为某个数字时的近似值,a_2n是n为其两倍时的近似值。它从n=4开始,a_n为2,然后应用公式直到n足够高。n越高,计算越精确,只是当n>=2^22时,它突然停止收敛到pi

以下是完整的代码:

import math
def pi(n):
    value = 2
    round = 4
    loop = 2
    while round != n:
        value= a2n(value,round)
        round = round*2
        loop +=1
        print("n=2^", loop, " pi = ", value)
    return value


def a2n(an,n):
    return  math.sqrt(-2*(math.sqrt((n**2)-4*(an**2))-n)*n)/2


print(pi(2**25))

我相信数学是好的,所以我认为python在处理较大的数字时遇到了问题。它从'3.141596553704'变为'3.1416742650217',并从那里变得更糟

这可能不是python本身的问题,但在2^22时,您会遇到python内部使用的类型的数值问题

对于python使用的64位浮点,可以得到52位尾数;这将精确度限制在逗号后面的16位(十进制)左右


编辑:我真的不明白回到Matlab有什么帮助——默认情况下,Matlab内部通常也使用相同的64位浮点类型。

可能不是python本身有这个问题,但在2^22时,您会遇到python内部使用的类型的数值问题

对于python使用的64位浮点,可以得到52位尾数;这将精确度限制在逗号后面的16位(十进制)左右


编辑:我真的不知道回到Matlab有什么帮助——Matlab内部默认情况下通常也使用相同的64位浮点类型。

您使用的迭代公式有些病态:随着迭代的进行,数量
n
变得比
an
大得多。所以在表达式中

math.sqrt(n**2-4*an**2)-n
平方根的结果将接近
n
,因此外部减法是两个几乎相等的量的减法(在相对意义上)。现在,如果您使用常规Python浮点计算,它有16位小数精度,那么随着迭代的进行,减法将得到一个只精确到少数数字的结果。有关此问题的更一般性的讨论,请参见上的Wikipedia页面

短篇故事:要使用您最初编写的公式获得
d
位数的
pi
,您需要在中间计算中使用比
d
位数多得多的位数。通过一点工作,您可以表明您需要在内部使用略多于
2d
精度的数字,以便获得
d
精确的圆周率数字。即使这样,你也必须小心,只需使用你所需要的迭代次数:太多的迭代,精度将再次丢失,无论你使用多少中间精度

但是这里有一个比将中间精度加倍更好的选择,那就是重写你的公式,这样就避免了失去意义。如果将
sqrt(n**2-4*an**2)-n
乘以共轭表达式
sqrt(n**2-4*an**2)+n
,则得到
-4*an**2
。因此,原始差异可以重写为
-4*an**2/(sqrt(n**2-4*an**2)+n)
。将其插入原始公式并稍微简化,将导致一个迭代步骤,如下所示:

def a2n(an, n):
    return an*math.sqrt(2*n/(math.sqrt(n*n-4*an*an)+n))
从数学上讲,这与您的
a2n
函数的计算完全相同,但从数值角度来看,它的表现要好得多

如果您使用这个迭代步骤代替原来的步骤,您将看到更小的舍入误差,并且您应该能够仅使用Python浮点就获得高达15位的精度。事实上,通过这个新的迭代运行您的代码,我在30次迭代后得到一个值
3.1415926535897927
,这仅仅是通过单个ulp(最后的单位)对pi的最佳双精度近似值

要获得更多的数字,我们需要使用模块。下面是一个代码片段,基于您的代码,但使用我建议的迭代修改计算,使用十进制模块获得精确到51位有效数字的pi值。它使用55位有效数字的内部精度,以允许舍入误差的累积

from decimal import getcontext

context = getcontext()
context.prec = 55    # Use 55 significant digits for all operations.
sqrt = context.sqrt  # Will work for both ints and Decimal objects,
                     # returning a Decimal result.

def step(an, n):
    return an*sqrt(2*n/(sqrt(n*n-4*an*an)+n)), n*2

def compute_pi(iterations):
    value, round = 2, 4
    for k in range(iterations):
        value, round = step(value, round)
    return value

pi_approx = compute_pi(100)
print("pi = {:.50f}".format(pi_approx))
结果如下:

pi = 3.14159265358979323846264338327950288419716939937511

对于原始公式,中间计算需要超过100的精度。

您使用的迭代公式有点病态:随着迭代的进行,数量
n
变得比
an
大得多。所以在表达式中

math.sqrt(n**2-4*an**2)-n
平方根的结果将接近
n
,因此外部减法是两个几乎相等的量的减法(在相对意义上)。现在,如果您使用常规Python浮点计算,它有16位小数精度,那么随着迭代的进行,减法将得到一个只精确到少数数字的结果。有关此问题的更一般性的讨论,请参见上的Wikipedia页面

短篇故事:要使用您最初编写的公式获得
d
位数的
pi
,您需要在中间计算中使用比
d
位数多得多的位数。通过一点工作,您可以表明您需要在内部使用略多于
2d
精度的数字,以便获得
d
精确的圆周率数字。即使这样,你也必须小心,只需使用你所需要的迭代次数:太多的迭代,精度将再次丢失,无论你使用多少中间精度

但是这里有一个比将中间精度加倍更好的选择,那就是重写你的公式,这样就避免了失去意义。如果将
sqrt(n**2-4*an**2)-n
乘以共轭表达式
sqrt(n**2-4*a