Python Pi 近似

Python Pi approximation

所以我必须用以下方法来近似 Pi:4*(1-1/3+1/5-1/7+1/9-...)。它也应该基于迭代次数。所以函数应该是这样的:

>>> piApprox(1)
4.0
>>> piApprox(10)
3.04183961893
>>> piApprox(300)
3.13825932952

但它是这样工作的:

>>> piApprox(1)
4.0
>>> piApprox(10)
2.8571428571428577
>>> piApprox(300)
2.673322240709928

我做错了什么?这是代码:

def piApprox(num):
    pi=4.0
    k=1.0
    est=1.0
    while 1<num:
        k+=2
        est=est-(1/k)+1/(k+2)
        num=num-1

    return pi*est

这就是您正在计算的内容:

4*(1-1/3+1/5-1/5+1/7-1/7+1/9...)

您可以通过在循环末尾添加 k += 2 来修复它:

def piApprox(num):
    pi=4.0
    k=1.0
    est=1.0
    while 1<num:
        k+=2
        est=est-(1/k)+1/(k+2)
        num=num-1
        k+=2
    return pi*est

您计算迭代次数的方式也是错误的,因为您当时添加了两个元素。

这是一个更简洁的版本,returns 您期望 10 次和 300 次迭代的输出:

def approximate_pi(rank):
    value = 0
    for k in xrange(1, 2*rank+1, 2):
        sign = -(k % 4 - 2)
        value += float(sign) / k
    return 4 * value

这是相同的代码,但更紧凑:

def approximate_pi(rank):
    return 4 * sum(-float(k%4 - 2) / k for k in xrange(1, 2*rank+1, 2))
def piApprox(num):                                                              
  pi=4.0                                                                      
  k=3.0                                                                       
  est=1.0                                                                     
  while 1<num:                                                                
    est=est-(1/k)+1/(k+2)                                                   
    num=num-1                                                               
    k+=4                                                                    

  return pi*est

也可用于实际任务math.pi

重要修改: 谁期望这个近似值产生圆周率——引自 Wikipedia:

It converges quite slowly, though – after 500,000 terms, it produces only five correct decimal digits of π

原回答: 这是一个有教育意义的例子。您尝试使用快捷方式并尝试通过在同一迭代中处理 k 的两个步骤来实现被加数的 "oscillating" 符号。但是,您每次迭代仅调整 k 一步。

通常,至少在数学中,(-1)**i 可以实现振荡符号。所以,我选择了这个来实现更具可读性:

def pi_approx(num_iterations):
    k = 3.0
    s = 1.0

    for i in range(num_iterations):
        s = s-((1/k) * (-1)**i)
        k += 2

    return 4 * s

如您所见,我稍微更改了您的方法,以提高可读性。您不需要在 while 循环中检查 num,也没有特别需要您的 pi 变量。你的est实际上是一个逐步增长的总和,为什么不叫它s("sum"是Python中的内置关键字)。根据您的公式,最后将总和乘以 4

测试:

>>> pi_approx(100)
3.1514934010709914

然而,收敛性不是特别好:

>>> pi_approx(100) - math.pi
0.009900747481198291

你的预期输出有点不稳定,因为你的 piApprox(300)(根据你的说法应该是 3.13825932952)离 PI 太远了。你是怎么想出来的?这可能会受到累积数值误差的影响吗?

编辑

关于函数在 10 次和 300 次迭代后应该 return 的内容,我不会太相信这本书。确实,经过 10 个步骤后的中间结果应该完全没有数字错误。在那里,你是否同时走两步k实际上是有区别的。所以这很可能是我的 pi_approx(10) 和书本之间的区别。对于 300 次迭代,数值错误可能会严重影响书中的结果。如果这是一本旧书,并且他们已经在 C 中实现了他们的示例,可能使用单精度,那么结果的很大一部分可能是由于数值误差的累积(注意:这是一个很好的例子,说明你有多糟糕受数值错误的影响:小值和大值的重复 sum,它不会变得更糟!)。

重要的是您已经研究了数学(圆周率的公式),并且您已经实现了近似该公式的工作 Python 版本。这就是本书的学习目标,所以继续解决下一个问题:-)。

这里有一个稍微简单的版本:

def pi_approx(num_terms):
    sign    = 1.                  # +1. or -1.
    pi_by_4 = 1.                  # first term
    for div in range(3, 2 * num_terms, 2):   # 3, 5, 7, ...
        sign     = -sign          # flip sign
        pi_by_4 += sign / div     # add next term
    return 4. * pi_by_4

这给出了

>>> for n in [1, 10, 300, 1000, 3000]:
...     print(pi_approx(n))

4.0
3.0418396189294032
3.1382593295155914
3.140592653839794
3.1412593202657186

虽然所有这些答案都是非常好的近似值,但如果您使用的是 Madhava-Leibniz 系列,那么根据本网站,您应该在前 21 个术语中得出"an approximation of π correct to 11 decimal places as 3.14159265359":https://en.wikipedia.org/wiki/Approximations_of_%CF%80

因此,更准确的解决方案可能是以下任何变体:

import math

def estimate_pi(terms):
    ans = 0.0
    for k in range(terms):
        ans += (-1.0/3.0)**k/(2.0*k+1.0)
    return math.sqrt(12)*ans

print(estimate_pi(21))

输出:3.141592653595635