该函数将浮动误差作为输入,并通过逐项计算该总和,直到误差和小于误差为止,将常数π²近似为误差之内。函数应该返回新的总和。

$$ \ pi ^ 2 = 8 + \ dfrac {8} {3 ^ 2} + \ dfrac {8} {5 ^ 2} + \ dfrac { 8} {7 ^ 2} + \ dfrac {8} {9 ^ 2} + \ cdots $$

示例:

approxPIsquared(0.0001)


结果是9.855519952254232

我的工作解决方案如下所示:

评论

可能您想返回prev(8 /(n * n)<=错误<8 /((n-1)*(n-1)))

您可能已经知道这一点,但以防万一:如果您想要一个足够好的pi平方近似值,而不是计算该特定公式,则将硬编码结果精确到浮点精度的极限,几乎可以采用任何度量,当然还有更好的性能。

您将很快用此方法舍入错误。

#1 楼



每次迭代时都不需要比较prevnew

新的和以前的总和之间的差只是当前项:$$ \ frac {8} {(2i + 1)^ 2} $$

如果您希望此术语小于error,则可以解决:

$$ \ mathrm {错误}> \ frac {8} {(2i + 1)^ 2} \\
\ iff(2i + 1)^ 2> \ frac {8} {error} \\
\ iff 2i +1> \ sqrt {\ frac {8} {error}} \\
\ iff i> \ frac {\ sqrt {\ frac {8} {error}}-1} {2} \\
$$

现在您知道系列应该有多少个术语,就可以直接返回结果:

添加增量

请注意,error表示项很小,而不是approx_pi_squared与π²之间有多近:

def approx_pi_squared(error):
    n = int(((8 / error)**0.5 - 1) / 2) + 1
    return sum(8 / (2 * i + 1)**2 for i in range(n))


即使有超过140 000个术语,该系列也只给出π²的前3个数字。这个公式非常简单,但是收敛太慢。

但是,有趣的是,math.pi**2approx_pi_squared(error)之间的区别似乎非常接近\ $ \ sqrt {2 \ mathrm {error}} \ $ 。对于任何error似乎都是成立的,因此我们可以更新函数:此新公式未经验证,因此使用风险自负!

BBP型公式

有很多π²公式,因此请随意选择一个。例如:

>>> import math
>>> approx_pi_squared(1e-10)
9.869590258918535
>>> math.pi**2 - approx_pi_squared(1e-7)
0.0004472271895057389
>>> math.pi**2 - approx_pi_squared(1e-10)
1.414217082285063e-05


approx_pi_squared(1e-10)似乎与error的数量级相同: math.pi**2 - approx_pi_squared(error)看起来像delta

使用Sympy

您可以将作业委派给(-1)**n * 2 * error,并确保以任意精度获得正确的结果:

def approx_pi_squared(error):
    n = int(((8 / error)**0.5 - 1) / 2) + 1
    delta = (2 * error)**0.5
    return sum(8 / (2 * i + 1)**2 for i in range(n)) + delta


评论


\ $ \ begingroup \ $
这是迄今为止最好的方法。
\ $ \ endgroup \ $
– Ma0
18 Mar 6 '18 at 12:19

\ $ \ begingroup \ $
@ Ev.Kounis,不,不是。它解决了我(先前的FWIW)答案中提出的第一点,但没有解决第二点,即关于数值分析和实际获得正确答案的问题。
\ $ \ endgroup \ $
– Peter Taylor
18 Mar 6 '18 at 12:36

\ $ \ begingroup \ $
@PeterTaylor:“正确答案”是使用更好的公式,因为该公式收敛太慢。误差= 1e-14时,需要1400万个项,sum(l)和sum(l [::-1])相距6.5e-11,但它们都距π²1.5e-7。不知何故,浮点数最大的总和要比另一个浮点数更好。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 6 '18 at 13:01

#2 楼

因此,您有一个多项式序列,并且希望在其项大于提供的公差时对它们的项求和(即:公差低于当前计算出的项)。

使用基本的python可以轻松表示构造/内建函数和itertools模块:



可以使用生成器表达式和无限计数器itertools.count描述无限多项式序列:

polynomial_sequence = (8 / (n * n) for n in itertools.count(1, 2))



使用itertools.takewhile可以在符合条件的情况下从可迭代(且生成器表达式为可迭代)中提取项:

approximate_finite_sequence = itertools.takewhile(tolerance.__lt__, polynomial_sequence)

< br here __lt__是写tolerance < …时调用的魔术方法,因此序列的项将保留while tolerance < term。但是与您的实现相反,低于容差的第一个项将不会保留,因此不会添加到近似值的总和中。


可以使用内置的sum

pi_squared = sum(approximate_finite_sequence)




将所有内容放在一起:

import itertools


def approximate_pi_squared(tolerance=0.0001):
    polynomial_sequence = (8 / (n * n) for n in itertools.count(1, 2))
    return sum(itertools.takewhile(tolerance.__lt__, polynomial_sequence))


评论


\ $ \ begingroup \ $
与OP相比,此解决方案乍一看似乎难以理解。有谁同意或者是因为我对Python不熟悉/不熟悉?
\ $ \ endgroup \ $
–真实
18 Mar 6 '18 at 18:34

\ $ \ begingroup \ $
直接调用诸如__lt __()之类的“丰富比较”魔术方法是一种不好的习惯。允许它们返回NotImplemented,在这种情况下,您应该交换操作数并尝试使用反向魔术方法。面对任意类型的不匹配,operator.lt()可以正确完成全部操作(尽管在这种情况下,您还需要functools.partial()才能通过公差)。
\ $ \ endgroup \ $
–凯文
18 Mar 6 '18 at 19:32



\ $ \ begingroup \ $
请记住,这不是样式问题,而是正确性问题。因此,如果您正在使用NumPy或另一个“有趣的”库来做某事,那么这种差异确实会给您带来麻烦。
\ $ \ endgroup \ $
–凯文
18年3月6日在19:37

\ $ \ begingroup \ $
@Kevin我也可以使用lambda diff:公差 \ $ \ endgroup \ $
–301_Moved_Permanently
18 Mar 6 '18 at 19:43

\ $ \ begingroup \ $
@Real对我而言,从以下意义上讲,本文中的解决方案更容易理解:如果同时给我提供了两段代码而又没有任何解释,那么对我来说,更容易解决“全局”问题与答案中的代码所做的相同,答案的代码在做什么(总结一系列)。但是对于其他许多人来说,答案的代码将难以理解。有经验的Python经验会影响这一点,这是有道理的。
\ $ \ endgroup \ $
– David Z
18年3月7日在10:19

#3 楼


请解决缩进问题(可能只是复制粘贴问题)。
在Python中,对于块语句(例如ifwhile)的表达式,不需要括号。您也不需要在表达式周围使用它(感谢E. Kounis)。
您可以使用i ** 2而不是i * i
您可以使用增量运算符:n += 2而不是n = n + 2
您应该使用i而不是n作为计数器变量(在大多数情况下[感谢Coal_])。

您可以使用(请确保使用import itertools;我个人更喜欢使用from itertools import count):

for n in itertools.count(3, 2):
    ...


而不是

n = 3
while (True):
    ...
    n = n + 2


不需要临时的diff变量。
对所有内容都使用snake_case但是类和“常量”。
您可以用return代替break ing(感谢@ hjpotter92)。

评论


\ $ \ begingroup \ $
而不是中断,只需返回new
\ $ \ endgroup \ $
– hjpotter92
18 Mar 6 '18 at 1:24

\ $ \ begingroup \ $
第4点在大多数情况下是无效的。仅仅因为大多数人使用i,j,k进行计数,并不意味着其他变量名称的有效性降低。例如,如果要遍历\ $(x,y)\ $对,则x和y比i和j更有意义。同样,如果在数学运算中将某些参数称为\ $ n \ $,则使用n帮助熟悉数学的人理解您的代码是有意义的。
\ $ \ endgroup \ $
–丹尼尔(Daniel)
18 Mar 6 '18 at 7:16

\ $ \ begingroup \ $
从数学角度来看,n通常是计数器的最大值,而当前值更有可能是k。
\ $ \ endgroup \ $
– allo
18 Mar 6 '18 at 10:47

\ $ \ begingroup \ $
您先计算new = prev +某物,然后再计算new-prev?
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 6 '18 at 11:10

\ $ \ begingroup \ $
@SolomonUcko说到不必要的括号,new =(prev + ...有很多。您可以编写new = prev + 8 / i ** 2
\ $ \ endgroup \ $
– Ma0
18 Mar 6 '18 at 12:17



#4 楼

由于所罗门·乌科(Solomon Ucko)对编码风格提供了出色的反馈,并针对您的原始算法进行了适当的修改,因此我想我将重点放在您添加的性能标签上。 pi,但让我们使用您提供的那一个,使其更快。通过在形式为10^(-n)的不同输入上运行函数,我发现您的程序执行了大约10**(n/2+.5)次迭代(大约减少了5-10%)。

一旦限制了迭代次数,我可以求助于numpy,它对于这类操作非常快。这是我最终使用的脚本:

import numpy as np

def approx_pi_squared_fast(decimals):
    n = int(10**(decimals/2+.5))
    denominators = np.arange(1, 2*n, 2)**2
    pi_squared = np.sum(8/denominators)
    return pi_squared


我将输入从error更改为decimals,因此新程序不会返回与它们完全相同的值你发布了。但是,它确实会为1-15之间的所有输入返回更精确的值(此后,脚本需要> 10s的时间进行测试)。它也比原始脚本更快地返回答案4-6倍。

编辑:编辑的函数名称

评论


\ $ \ begingroup \ $
好抓住!对于较小的小数位数(<10),似乎可以提高50-100%。
\ $ \ endgroup \ $
– maxb
18 Mar 6 '18 at 9:37

\ $ \ begingroup \ $
计算确切的n确实不是很困难,您的方法应称为rox_pi_squared_fast BTW;)除此之外,它看起来不错,并且比其他方法快得多。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 6 '18 at 11:51



\ $ \ begingroup \ $
可以找到一个更准确的n值,但我主要是想在同一个球场上,并且对于任何小数位数(包括浮点数),它都要大于原始问题中的迭代数)。
\ $ \ endgroup \ $
– maxb
18 Mar 6 '18 at 11:57

#5 楼

Ockham的剃刀(不必将实体不必要地相乘)也可以应用于变量。您知道diff,因此您可以将prevnew简化为一个变量,并使用更具参考性的名称(例如sum):
区别是什么,我们可以解决最大的问题:正确性。若要正确求和浮点数列表,应从最小的数字开始,而不是最大的数字。但是,由于我们现在有了一个作为diff的函数的n的简单表达式,因此我们可以使用n对其求反以找到该项小于所需误差的sqrt的第一个值。

评论


\ $ \ begingroup \ $
在这种情况下,颠倒这些条款似乎并没有太大影响。您是否有一个示例可以带来显着差异?
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 6 '18 at 16:36

\ $ \ begingroup \ $
@EricDuminil:如果将误差设置得足够小,它将有所作为。具体来说,如果小于1E-15,则相继添加较小的项将不会对输出值产生任何影响。
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
18 Mar 7 '18 at 14:46

\ $ \ begingroup \ $
@MartinBonner:错误= 1e-15,公式仍会偏离4.​​5e-8。这可能是重要的一点,我只是认为它不适用于这种情况。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 7 '18 at 15:02

\ $ \ begingroup \ $
@EricDuminil,尽管有问题的标题,但给出的实际规格要求特定的总和,而我认为相关的是计算该总和的错误,而不是相对于\ $ \ pi ^ 2 \ $。
\ $ \ endgroup \ $
– Peter Taylor
18 Mar 7 '18 at 17:22

\ $ \ begingroup \ $
请不要使用名称sum。这是内置的python。
\ $ \ endgroup \ $
–蛇和咖啡
18 Mar 8'8 at 6:42

#6 楼

现有的所有答案都提供了有价值的反馈,但是我认为缺乏对循环操作符选择的讨论。在您的初始算法中,您使用了while True:if... break。为什么不将要评估的条件放在if中,例如while之类?

优点是可以立即弄清楚停止条件是什么,从而使您的代码更易于理解。至少在我看来,这是所罗门·乌科(Solomon Ucko)原本不错的答案中的一个默认值。他的while diff > error:没有提及终止条件。

评论


\ $ \ begingroup \ $
您的建议后的问题是,在第一次迭代中diff的值是多少
\ $ \ endgroup \ $
– A. Romeu
18 Mar 6 '18 at 12:37

\ $ \ begingroup \ $
@ A.Romeu不管您想要什么,只要它大于错误。错误+ 1将起作用。
\ $ \ endgroup \ $
–尼科
18 Mar 6 '18 at 13:51

#7 楼

重写此问题的另一种方法是使用递归。如果不符合错误范围,可以通过以下操作将其重新提供给函数:

边界,那么您将再次执行该函数,但使用函数中的先前值和i+2。我认为,即使没有节省任何时间,它看起来也会更干净。

由于您只近似于某个小数,因此您可能还希望仅在您的错误所限制的小数位,因为后面的所有内容都不确定。您可以使用round()函数来做到这一点,如下所示:因此输出与小数位数匹配作为错误输入。因此,如果运行此命令,则会得到approx_pi_squared(0.0001),返回9.8555。查找pi * pi,我发现它更接近9.8696。您的公式来自哪里?我正在尝试找到它,但是找不到。

评论


\ $ \ begingroup \ $
您知道Python中的递归限制吗?您将无法比错误= 0.00001少得多……
\ $ \ endgroup \ $
–301_Moved_Permanently
18 Mar 6 '18 at 13:41

\ $ \ begingroup \ $
是的,我知道。我给出的答案是思考问题的另一种方式。我不确定OP的意图,还是不确定他们是否需要降低报价。
\ $ \ endgroup \ $
–TheStrangeQuark
18 Mar 6 '18 at 13:46



\ $ \ begingroup \ $
对于快速收敛的系列,递归可能会很有趣。对于OP的情况而言,这没有多大意义,但是对于Ramanujan的公式来说,它可以很好地工作。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18 Mar 6 '18 at 14:34

\ $ \ begingroup \ $
是的,我同意。我没有调查OP公式的收敛速度,甚至没有收敛到有意义的东西。
\ $ \ endgroup \ $
–TheStrangeQuark
18 Mar 6 '18 at 15:57

#8 楼

当您添加大量浮点数时,请看一下Kahan求和,它看起来很复杂,但是却减少了舍入误差。

#9 楼

通过积分测试,第一个\ $ N \ $项的部分和与极限的距离约为
$$ sum_ {n = N} ^ \ infty \ frac {8} {(2n + 1)^ 2} \ simeq \ int_ {N-0.5} ^ \ infty \ frac2 {(x + 0.5)^ 2} dx = \ frac2 {N}。$$
这表明经常被报道的事实是获得大约1e-14的精度,您需要部分和中的1e14项。 \ frac {8} {(2n + 1)^ 2}-\ frac2N&= \ sum_ {n = N} ^ \ infty \ frac {8} {(2n + 1)^ 2}-\ left(\ frac2n- \ frac2 {n + 1} \ right)\\&=-\ sum_ {n = N} ^ \ infty \ frac {1} {2n(n + 0.5)^ 2(n + 1)} \ simeq- \ frac1 {6N ^ 3} \ end {align}

现在使用第一个误差估计将部分和校正为\ $ \ sum_ {i = 0} ^ {N-1} \ frac {8} {(2i + 1)^ 2} + \ frac2N \ $。那么,该公式的误差估计就是上面的第二个误差估计。因此,要使用校正后的公式得出\ $ε\ $的错误,需要使用\ $ N = 1 / \ sqrt [3] {6ε} \ $项。例如,要获得\ $ε= 10 ^ {-14} \ $,则必须总结大约\ $ N = 2.6 \ cdot 10 ^ 4 \ $个条件。

使用从最小到最大的总和来实现此目的,以减轻浮点噪声累积

def approx_pi_squared(error):
    n = int( 1 / (6*error)**(1/3) ) + 1
    return sum(8 / (2 * (n - i) - 1)**2 for i in range(n)) + 2 / n

for k in range(4,17):
     pi2 = approx_pi_squared(10**-k);
     print "%2d   %20.15f   %10.5e"%(k, pi2, pi2-pi**2)


给出预期的结果

k       approx pi squared     error

 4      9.869700618552868   9.62175e-05
 5      9.869613878813270   9.47772e-06
 6      9.869605350023797   9.48934e-07
 7      9.869604499989549   9.89002e-08
 8      9.869604411023413   9.93406e-09
 9      9.869604402085667   9.96309e-10
10      9.869604401189266   9.99076e-11
11      9.869604401099350   9.99201e-12
12      9.869604401090358   1.00009e-12
13      9.869604401089459   1.01252e-13
14      9.869604401089369   1.06581e-14
15      9.869604401089360   1.77636e-15
16      9.869604401089360   1.77636e-15