流言: 在实际使用中,使用不同的ε值来比较浮点数没有什么区别
正确性: 错误
通常当你在包含浮点数计算的任务的SRM之后到Round Tables看看你会看到有人发出像“我把精度从1e-12改成1e-7就通过了practice room所有的系统测试”这样的消息。
这样的讨论的例子还有: here, here, here, here 和 here.(它们都是值得一读的,从其他人的错误中学习要比从自己当中少痛苦一点)
我们将通过展示另外一个简单的例子来开始我们的解答。
这个程序将会打印多少个点呢?这次很清楚,不是吗?这次的终止条件不再是等于测试。这个循环将会在10^22遍历后停止。或者。。是吗?
真糟糕,这又一次的成了死循环。为什么会这样呢?因为当r这个值变得大了,这个变量的精度并不能够足够大的存储所有r的十进制下的数。最后一些会丢失掉。因此当我们在这么大的一个数后加1,结果又被近似到原来的数了。
练习:试着判断一下我们这个循环里r所能达到的最大值。检查你的答案,如果你的判断出错了,找出它的原因。
在作出观察之后,我们将显示为什么表达式fabs(a-b)<epsilon(使用固定值epsilon,推荐在1e-7到1e-9)对于比较双精度数并不是理想的。
考虑值123456123456.1234588623046875和123456123456.1234741210937500。它们都没什么特别之处,仅仅是两个double可以在不进行近似的情况下就能存储的两个值。他们直接的差大约在2e-5.
现知让我们看看这两个值的位形式:
second: 01000010 00111100 10111110 10001110 11110010 01000000 00011111 10011100
是这样的,没错。这是在double中可以被存储的两个连续的值。任何使用近似得到的错误都能够使得其中对一个变成另外一个(或者超过)。但是他们仍然是不一样的,因此我们原始的测试“相等”不能够工作。
我们真正需要的是容忍一些小的精度误差。正如我们所看到的,double能够近似存储最多15个10进制数字的数。通过近似积聚的精度错误,最后一些数字将会丢失。但是我们究竟应该怎样容忍这些误差呢?
我们将不使用固定常数ε,而使用一个与比较的数数量级相关的值。更确切点说,如果x是一个双精度数,那么x*1e-10是一个比x小10倍数量级的数。它的最高有效位对应于x的第11位最高有效位。这使得它能够很好的满足我们的需要。
换句话说,一个更好的方式来比较a,b两个双精度数是否“相等”就是检查a是否在b*(1-1e-10)和b*(1+1e-10)之间。(小心,如果b是负数的时候,这两个数中的第一个将会更大!)
看到用这样的比较方式的问题了吗?试着比较1e-1072和-1e1072.这两个数都机会相等并且等于0,但是我们的测试在处理这种情况时会失败。这就是为什么我们既需要做第一个测试(测试绝对误差)和第二个测试(测试相对误差)。
这就是TC中用来检查你的返回值是否正确的方法。现在你知道原因了。
有更好的比较函数(参见其中的一篇参考文章),但是更重要的是要知道在实际中你经常仅仅使用绝对误差测试而侥幸成功。为什么?
因为包含在计算当中的数字都是在限定的范围之内。例如,如果我们需要比较的最大数只是9947,那么你知道一个double能够在十进制小数点后存储另外的11个数字。因此我们在进行绝对误差测试时使用epsilon=1e-8,我们运行最后的3个数字丢掉。
这个方法的优点很明确:检查绝对误差要比上面的高级测试简单。
- Elections (a Div2 easy with a success rate of only 57.58%)
- Archimedes
- SortEstimate (the binary search is quite tricky to get right if you don't understand precision issues)
- PerforatedSheet (beware, huge rounding errors possible)
- WatchTower
- PackingShapes
农夫三拳:http://www.cnblogs.com/drizzlecrj/archive/2007/02/27/658726.html
ps。看明白了没,反正我是没看明白。