Calculate PI using sum of inverse squares
我需要使用以下公式以预先定义的精度计算π:
所以我得到了这个解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private static double CalculatePIWithPrecision(int presicion) { if (presicion == 0) { return PI_ZERO_PRECISION; } double sum = 0; double numberOfSumElements = Math.Pow(10, presicion + 2); for (double i = 1; i < numberOfSumElements; i++) { sum += 1 / (i * i); } double pi = Math.Sqrt(sum * 6); return pi; } |
所以这是正确的,但我面临的问题是效率。它非常慢,精度值为8或更高。
有更好(更快)的吗?用这个公式计算π的方法?
1 | double numberOfSumElements = Math.Pow(10, presicion + 2); |
我将严格用实际软件工程术语来讨论这个问题,避免在形式数学中迷失方向。只是任何软件工程师都应该知道的实用技巧。
首先观察代码的复杂性。执行所需的时间由该表达式严格确定。你已经写了一个指数算法,你计算的值会随着预测值的增加而迅速增加。你引用不舒服的数字,8产生10^10或者一个循环,可以计算100亿。是的,你注意到了这一点,那就是计算机开始花费几秒钟来产生结果,不管它们有多快。
指数算法不好,性能很差。只有一个具有阶乘复杂度的函数才能做得更糟,o(n!),上升得更快。否则,许多现实问题的复杂性。
现在,这个表达真的准确吗?您可以通过"肘部测试"来实现这一点,使用一个实际的封底示例。让我们选取5位数的精度作为目标,并写出:
1 | 1.0000 + 0.2500 + 0.1111 + 0.0625 + 0.0400 + 0.0278 + ... = 1.6433 |
可以看出,添加的内容会很快变小,并且会很快收敛。你可以推断出,一旦你添加的下一个数字变得足够小,那么它几乎不会使结果更准确。假设下一个数字小于0.00001,那么是时候停止尝试改进结果了。
所以你会停在1/(n*n)=0.00001=>n*n=100000=>n=sqrt(100000)=>n~=316
你的表达方式是10^(5+2)=10000000
您可以看出,您还差得很远,循环的频率太高,并且在最近的9999万次迭代中无法提高结果的准确性。
是时候讨论真正的问题了,太糟糕了,以至于你没有解释你是如何得出如此严重的错误算法的。但在测试代码时,您肯定发现它并不擅长计算更精确的圆周率值。因此,您认为通过更频繁地迭代,可以得到更好的结果。
请注意,在这个肘部测试中,能够以足够的精度计算加法也是非常重要的。我故意把数字四舍五入,就好像它是在一台能精确地加5位数的机器上计算出来的一样。不管你做什么,结果永远不会比5位数更精确。
您正在代码中使用双类型。直接由处理器支持,它没有无限的精度。唯一需要记住的一条规则是,使用双精度的计算永远不会比15位数更精确。还要记住浮动规则,它永远不会比7位数更精确。
因此,无论您传递给Presicion的值是多少,结果都不会比15位更精确。这根本没用,你已经有了精确到15位的π值。这是数学。
要解决这个问题,您需要做的一件事是使用比double精度更高的类型。事实上,它需要是一个具有任意精度的类型,它至少需要和您传递的Presicion值一样精确。这种类型在.NET框架中不存在。找到一个可以为您提供一个的库是SO中的一个常见问题。