How dangerous is it to compare floating point values?
我知道
但每次我想检查,例如,
1 2 3 | if (theView.frame.origin.x == 0) { // do important operation } |
与
当比较时,
首先,浮点值的行为不是"随机的"。精确的比较可以而且确实在许多现实世界中有意义。但是如果你要使用浮点,你需要知道它是如何工作的。假设浮点数的工作方式与实数类似,这会使代码很快中断。错误的一面是,假设浮点结果具有与之相关的大型随机模糊(就像这里的大多数答案所建议的那样),将会得到一个代码,该代码起初似乎可以工作,但最终会出现较大的数量级错误和断角情况。好的。
首先,如果要用浮点编程,应阅读以下内容:好的。
每一个计算机科学家都应该知道什么是浮点运算好的。
是的,全部读完。如果负担太大,您应该使用整数/固定点进行计算,直到有时间阅读为止。-)好的。
现在,尽管如此,精确的浮点比较的最大问题归结为:好的。
事实上,您可以在源代码中写入或使用
由于没有足够的精度来表示实际结果,许多结果被舍入。您可以看到的一个简单示例是将
由于需要无限多的位置才能得到正确的值,所以许多结果被舍入。这既包括1/3这样的有理结果(你很熟悉十进制中取无穷多个位置),也包括1/10(二进制中也取无穷多个位置,因为5不是2的幂),以及不合理的结果,比如任何不完美平方的平方根。好的。
双圆角。在某些系统(尤其是x86)上,浮点表达式的计算精度高于其名义类型。这意味着,当上述四舍五入类型之一发生时,您将得到两个四舍五入步骤,首先将结果四舍五入为更高精度类型,然后四舍五入为最终类型。例如,考虑一下如果将1.49舍入到整数(1)中会发生什么,而如果先将其舍入到一个小数位(1.5),然后将结果舍入到整数(2)中会发生什么。这实际上是浮点处理的最糟糕的领域之一,因为编译器的行为(尤其是对于有缺陷的、不符合规范的编译器,如gcc)是不可预测的。好的。
超越函数(
当您编写浮点代码时,需要记住您对可能导致结果不精确的数字所做的操作,并进行相应的比较。通常情况下,与"epsilon"比较是有意义的,但是epsilon应该基于所比较的数字的大小,而不是绝对常数。(在绝对常数epsilon可以工作的情况下,这强烈表明固定点(而不是浮点)是工作的正确工具!)好的。
编辑:特别是,幅度相对epsilon检查应该如下所示:好的。
如果
最后,请注意,如果使用它,可能需要在接近零的位置进行一些特别的注意,因为
同样,如果使用双打,则用
因为0完全可以表示为IEEE754浮点数(或者使用我曾经使用过的任何其他F-P数字实现),所以与0进行比较可能是安全的。但是,如果您的程序计算一个值(如
为了澄清一点,计算如下:
1 | areal = 0.0 |
将(除非您的语言或系统被破坏)创建一个值,使(areal==0.0)返回true,但另一个计算(如
1 | areal = 1.386 - 2.1*(0.66) |
可能不会。
如果你能向自己保证你的计算产生的值是0(而不仅仅是它们产生的值应该是0),那么你就可以继续把f-p值和0进行比较。如果你不能保证达到要求的程度,最好还是坚持"容忍的平等"的常规方法。
在最坏的情况下,对f-p值的粗心比较可能是极其危险的:想想航空电子设备、武器制导、电厂运行、车辆导航,以及几乎所有计算符合现实世界的应用。
对愤怒的小鸟来说,没有那么危险。
我想给出一个与其他人不同的答案。他们很适合回答你所说的问题,但可能不适合你需要知道什么或你真正的问题是什么。
图形中的浮点很好!但几乎没有必要直接比较浮动。你为什么要这么做?图形使用浮动定义间隔。如果一个float在一个同样由float定义的区间内,那么比较总是定义得很好,只需要保持一致,而不是精确!只要一个像素(这也是一个间隔!)可以分配,这是所有图形的需要。
所以,如果你想测试你的点是否在一个[0..width[范围]之外,这很好。只要确保一致地定义包含。例如,始终定义内部是(x>=0&;x 但是,如果您滥用图形坐标作为某种标志,例如查看窗口是否停靠,则不应执行此操作。使用与图形表示层分离的布尔标志。
与零比较是一种安全的操作,只要零不是一个计算值(如上面的答案所述)。原因是零是浮点中一个完全可表示的数字。
说的是完全可表示的值,你得到24位的范围在两个概念的力量(单精度)。所以1,2,4是完全可代表的,如.5,.25和.125。只要你所有的重要部分都是24位的,你就是黄金。所以10.625可以精确地表示。
这很好,但在压力下会很快崩溃。两个场景浮现在脑海中:1)涉及计算时。不要相信那个sqrt(3)*sqrt(3)==3。只是不会那样。它可能不会像其他一些答案所暗示的那样在一个epsilon内。2)当涉及非2次幂(NPOT)时。所以这听起来可能很奇怪,但是0.1是一个无穷大的二进制级数,因此任何涉及这样一个数字的计算从一开始都是不精确的。
(原来的问题提到了与零的比较。不要忘记-0.0也是一个完全有效的浮点值。)
[正确答案]掩盖了选择
正是由于舍入错误,不应将"精确"值的比较用于逻辑运算。在视觉显示器上的特定位置的情况下,如果位置为0.0或0.000000000003,则不可能有任何影响-这种差异对眼睛来说是不可见的。所以你的逻辑应该是:
1 2 | #define VISIBLE_SHIFT 0.0001 // for example if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ } |
然而,最终,"肉眼看不见"将取决于您的显示属性。如果可以上界显示(应该可以),那么选择
现在,正确的答案取决于
K is a constant you choose such that the accumulated error of your
computations is definitely bounded by K units in the last place (and
if you're not sure you got the error bound calculation right, make K a
few times bigger than what your calculations say it should be)
所以我们需要
我们将使用"正确答案"的详细信息:
让我们试试k的所有值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | #include <math.h> #include <float.h> #include <stdio.h> void main (void) { double x = 1e-13; double y = 0.0; double K = 1e22; int i = 0; for (; i < 32; i++, K = K/10.0) { printf ("K:%40.16lf ->", K); if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN) printf ("YES "); else printf ("NO "); } } ebg@ebg$ gcc -o test test.c ebg@ebg$ ./test K:10000000000000000000000.0000000000000000 -> YES K: 1000000000000000000000.0000000000000000 -> YES K: 100000000000000000000.0000000000000000 -> YES K: 10000000000000000000.0000000000000000 -> YES K: 1000000000000000000.0000000000000000 -> YES K: 100000000000000000.0000000000000000 -> YES K: 10000000000000000.0000000000000000 -> YES K: 1000000000000000.0000000000000000 -> NO K: 100000000000000.0000000000000000 -> NO K: 10000000000000.0000000000000000 -> NO K: 1000000000000.0000000000000000 -> NO K: 100000000000.0000000000000000 -> NO K: 10000000000.0000000000000000 -> NO K: 1000000000.0000000000000000 -> NO K: 100000000.0000000000000000 -> NO K: 10000000.0000000000000000 -> NO K: 1000000.0000000000000000 -> NO K: 100000.0000000000000000 -> NO K: 10000.0000000000000000 -> NO K: 1000.0000000000000000 -> NO K: 100.0000000000000000 -> NO K: 10.0000000000000000 -> NO K: 1.0000000000000000 -> NO K: 0.1000000000000000 -> NO K: 0.0100000000000000 -> NO K: 0.0010000000000000 -> NO K: 0.0001000000000000 -> NO K: 0.0000100000000000 -> NO K: 0.0000010000000000 -> NO K: 0.0000001000000000 -> NO K: 0.0000000100000000 -> NO K: 0.0000000010000000 -> NO |
如果我希望1e-13为"零",那么k应该是1e16或更大。
所以,我想说你有两个选择:
正确的问题:如何比较可可中的点?
正确答案:cgPointEqualTopoint()。
另一个问题:两个计算值是否相同?
这里的答案是:他们没有。
如何检查它们是否接近?如果要检查它们是否接近,则不要使用cgPointEqualTopoint()。但是,不要检查它们是否接近。做一些在现实世界中有意义的事情,比如检查一个点是否在一条线之外,或者一个点是否在一个球体内。
上次我检查C标准时,没有要求双精度浮点运算(总共64位,尾数53位)的精度高于该精度。但是,一些硬件可能在寄存器中执行更高精度的操作,并且该要求被解释为不需要清除低阶位(超出加载到寄存器中的数字精度)。所以你可能会得到意想不到的比较结果,就像这样,这取决于登记簿中最后一个睡在那里的人留下了什么。
也就是说,尽管我每次看到它时都会努力将其删除,但我工作的机构中有许多C代码是使用gcc编译的,并在Linux上运行,很长一段时间内我们都没有注意到这些意外的结果。我不知道这是否是因为GCC正在为我们清除低阶位,80位寄存器不用于现代计算机上的这些操作,标准已经更改,或者什么。我想知道是否有人能引用章节。
您可以使用这样的代码来比较浮点数和零:
1 2 3 | if ((int)(theView.frame.origin.x * 100) == 0) { // do important operation } |
这将与0.1精度进行比较,在这种情况下,这足以满足cgfloat的要求。
1 2 3 4 5 6 7 8 9 10 | -(BOOL)isFloatEqual:(CGFloat)firstValue secondValue:(CGFloat)secondValue{ BOOL isEqual = NO; NSNumber *firstValueNumber = [NSNumber numberWithDouble:firstValue]; NSNumber *secondValueNumber = [NSNumber numberWithDouble:secondValue]; isEqual = [firstValueNumber isEqualToNumber:secondValueNumber]; return isEqual; |
}
我认为正确的做法是将每个数字声明为一个对象,然后在该对象中定义三个内容:1)相等运算符。2)设定可接受差分法。3)值本身。如果两个值的绝对差小于设置为可接受的值,则相等运算符返回true。
您可以对对象进行子类化以适应问题。例如,如果圆钢直径相差小于0.0001英寸,则1至2英寸之间的金属圆钢可被视为直径相等。所以您可以使用参数0.0001调用setAcceptableDifference,然后使用带信心的相等运算符。