$$ \ 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
我的工作解决方案如下所示:
#1 楼
每次迭代时都不需要比较
prev
和new
。新的和以前的总和之间的差只是当前项:$$ \ 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**2
和approx_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:公差
–301_Moved_Permanently
18 Mar 6 '18 at 19:43
\ $ \ begingroup \ $
@Real对我而言,从以下意义上讲,本文中的解决方案更容易理解:如果同时给我提供了两段代码而又没有任何解释,那么对我来说,更容易解决“全局”问题与答案中的代码所做的相同,答案的代码在做什么(总结一系列)。但是对于其他许多人来说,答案的代码将难以理解。有经验的Python经验会影响这一点,这是有道理的。
\ $ \ endgroup \ $
– David Z
18年3月7日在10:19
#3 楼
请解决缩进问题(可能只是复制粘贴问题)。
在Python中,对于块语句(例如
if
和while
)的表达式,不需要括号。您也不需要在表达式周围使用它(感谢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
,因此您可以将prev
和new
简化为一个变量,并使用更具参考性的名称(例如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
评论
可能您想返回prev(8 /(n * n)<=错误<8 /((n-1)*(n-1)))您可能已经知道这一点,但以防万一:如果您想要一个足够好的pi平方近似值,而不是计算该特定公式,则将硬编码结果精确到浮点精度的极限,几乎可以采用任何度量,当然还有更好的性能。
您将很快用此方法舍入错误。