比较浮点值有多危险?

Pro*_*ber 381 c floating-point objective-c floating-accuracy ios

我知道因UIKit使用CGFloat分辨率独立坐标系而使用.

但我想,以检查是否例如每次frame.origin.x0它让我感到恶心:

if (theView.frame.origin.x == 0) {
    // do important operation
}
Run Code Online (Sandbox Code Playgroud)

是不是CGFloat有比较时容易误报==,<=,>=,<,>?这是一个浮点,他们有不寻常的问题:0.0000000000041例如.

Objective-C在比较时是在内部处理这个,还是可能发生origin.x读取为零的情况并不是0真实的?

R..*_*R.. 457

首先,浮点值的行为不是"随机"的.在大量现实世界的用法中,精确的比较可以而且确实有意义.但是如果你要使用浮点数,你需要知道它是如何工作的.假设浮点工作就像实数一样,会让你快速破解代码.假设浮点结果与它们相关的大量随机模糊(就像这里建议的大多数答案一样)会让你看起来最初工作的代码,但最终会出现大幅度错误和破坏角落情况.

首先,如果你想用浮点编程,你应该读这个:

每个计算机科学家应该知道的浮点运算

是的,阅读全部内容.如果这是一个太大的负担,你应该使用整数/不动点进行计算,直到你有时间阅读它.:-)

现在,有了这个说法,精确浮点比较的最大问题归结为:

  1. 该地段价值的,你可以在源写,或者用阅读的事实scanf或者strtod,不存在浮动点值并获得静悄悄地转换为最接近的近似.这就是demon9733的答案所说的.

  2. 事实上许多结果由于没有足够的精度来表示实际结果而被舍入.一个简单的例子,你可以看到这是添加x = 0x1fffffey = 1浮动.这里,x尾数中有24位精度(ok)并且y只有1位,但是当你添加它们时,它们的位不在重叠位置,结果需要25位精度.相反,它会四舍五入(到0x2000000默认的舍入模式).

  3. 事实上许多结果由于需要无限多的位置来获得正确的值而被舍入.这包括合理的结果,如1/3(你从十进制熟悉,它需要无限多的地方),但也是1/10(这也是二进制中无限多的地方,因为5不是2的幂),以及不合理的结果,如任何不完美正方形的平方根.

  4. 双舍入.在某些系统(特别是x86)上,浮点表达式的精度高于其标称类型.这意味着当上述类型的舍入之一发生时,您将获得两个舍入步骤,首先将结果舍入到更高精度类型,然后舍入到最终类型.例如,如果将1.49舍入为整数(1),请考虑以十进制发生的情况,而如果首先将其舍入到一个小数位(1.5),然后将结果舍入为整数(2),则会发生什么.这实际上是浮点处理的最糟糕的区域之一,因为编译器的行为(特别是对于有缺陷的,不符合要求的编译器,如GCC)是不可预测的.

  5. 超越函数(trig,exp,log,等)不规定为具有正确的舍入结果; 结果只是指定在最后一个精度位置(通常称为1ulp)的一个单位内是正确的.

当您编写浮点代码时,您需要记住您正在对可能导致结果不准确的数字执行的操作,并相应地进行比较.通常,与"epsilon"进行比较是有意义的,但是epsilon应该基于您所比较的数字大小,而不是绝对常数.(在绝对常数epsilon可以工作的情况下,这强烈表明固定点,而不是浮点,是工作的正确工具!)

编辑:特别是,幅度相对的epsilon检查应该类似于:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Run Code Online (Sandbox Code Playgroud)

FLT_EPSILON从定float.h(含更换DBL_EPSILONdoubleS或LDBL_EPSILONlong doubleS)和K是您选择这样的计算的累积误差绝对是以下所界定的恒定K在最后的地方单位(如果你不知道你得到了错误绑定计算权,K比你的计算所说的要大几倍.

最后,请注意,如果你使用它,可能需要在零附近FLT_EPSILON做一些特别小心,因为对于非正规数没有意义.快速解决方法是:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
Run Code Online (Sandbox Code Playgroud)

并且DBL_MIN如果使用双打同样替代.

  • 如果`x`和`y`有不同的符号,那就没问题了.右边将是"太小",但由于"x"和"y"具有不同的符号,所以它们不应该相等.(除非它们非常小,以至于非常规,但第二种情况会发现它) (26认同)
  • 如果`x`和`y`(可以)有不同的符号,`fabs(x + y)`是有问题的.仍然是一个很好的答案,反对货物崇拜比较的潮流. (24认同)
  • *"首先,浮点值在他们的行为中不是"随机的".确切的比较可以而且确实在大量的现实世界中使用."* - 只有两个句子并且已经赢得了+1!这是人们在使用浮点时最令人不安的误解之一. (16认同)
  • 我很好奇你的陈述:"特别是对于像GCC这样的有缺陷的,不符合要求的编译器".真的是GCC越野车也不符合规定吗? (3认同)
  • 由于问题是标签的iOS,这是值得注意的是,苹果的编译器(两者铛和苹果的GCC编译)一直使用FLT_EVAL_METHOD = 0,并试图完全严密的关于不携带过多的精度.如果您发现任何违规行为,请提交错误报告. (3认同)
  • @AlbertRenshaw:那不是 C。它是一个 gcc 功能,仅用于实现内部。`float.h` 是一个标准的独立标头,没有理由不包含它。 (2认同)

Hig*_*ark 35

由于0可以完全表示为IEEE754浮点数(或使用我曾经使用的任何其他fp数字实现),因此与0进行比较可能是安全的.但是,如果你的程序计算一个theView.frame.origin.x你有理由认为应该为0但你的计算不能保证为0 的值(例如),你可能会被咬.

为了澄清一点,计算如下:

areal = 0.0
Run Code Online (Sandbox Code Playgroud)

将(除非您的语言或系统被破坏)创建一个值,使得(areal == 0.0)返回true但另一个计算如

areal = 1.386 - 2.1*(0.66)
Run Code Online (Sandbox Code Playgroud)

不得.

如果你可以向自己保证你的计算产生0的值(而不仅仅是它们产生的值应该是0)那么你可以继续将fp值与0进行比较.如果你不能保证自己达到所需的程度,最好坚持"宽容平等"的惯常做法.

在最糟糕的情况下,对fp值的粗心比较可能是非常危险的:思考航空电子设备,武器引导,发电厂运行,车辆导航,几乎任何计算符合现实世界的应用.

对于愤怒的小鸟,没有那么危险.

  • 实际上,`1.30 - 2*(0.65)`是一个表达式的完美示例,如果你的编译器实现了IEEE 754,它显然会求值为0.0,因为表示为0.65`和`1.30`的双精度具有相同的有效数,并且乘以两个显然是准确的. (11认同)
  • 仍然得到这个代表,所以我改变了第二个示例片段. (6认同)

sta*_*ole 22

我想给出一些与其他答案不同的答案.它们非常适合回答您所述的问题,但可能不适合您需要了解的内容或您的真正问题.

图形中的浮点很好!但是几乎没有必要直接比较浮子.你为什么要这么做?Graphics使用浮点数来定义间隔.并且比较浮点是否也在浮点数定义的区间内总是很明确,只需要一致,不准确或精确!只要一个像素(也是一个间隔!)可以分配所有图形需要.

因此,如果你想测试你的点是否在[0..width [范围之外]这是正常的.只需确保一致地定义包含.例如,始终定义内部是(x> = 0 && x <width).交叉点或命中测试也是如此.

但是,如果您将图形坐标滥用为某种标记,例如,如果要查看窗口是否已停靠,则不应执行此操作.请使用与图形表示层分开的布尔标志.


小智 13

只要零不是计算值(如上面的答案中所述),与零比较可以是安全的操作.原因是零是浮点数中完全可表示的数字.

说出完全可表示的值,您可以获得24位的功率范围(单精度).因此,1,2,4是完全可表示的,如.5,.25和.125.只要你的所有重要位都是24位,你就是金色的.所以10.625可以准确地表达.

这很好,但会在压力下迅速崩溃.我想到两种情况:1)涉及计算时.不要相信sqrt(3)*sqrt(3)== 3.它不会那样.并且它可能不会在epsilon中,正如其他一些答案所暗示的那样.2)当涉及任何非幂2(NPOT)时.所以它可能听起来很奇怪,但0.1是二进制的无限级数,因此涉及这样的数字的任何计算从一开始就是不精确的.

(哦,原始问题提到比较为零.不要忘记-0.0也是一个完全有效的浮点值.)


GoZ*_*ner 11

['正确答案'掩盖了选择K.选择K最终就像选择VISIBLE_SHIFT但是选择一样K不明显,因为VISIBLE_SHIFT它不是基于任何显示属性.因此,选择你的毒药 - 选择K或选择VISIBLE_SHIFT.这个答案提倡选择VISIBLE_SHIFT然后证明选择的难度K]

正是由于圆形错误,您不应该使用逻辑运算的"精确"值进行比较.在您在视觉显示器上的位置的特定情况下,如果位置是0.0或0.0000000003则无关紧要 - 眼睛看不到差异.所以你的逻辑应该是这样的:

#define VISIBLE_SHIFT    0.0001        // for example
if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

但是,最终,"眼睛看不见"将取决于您的显示属性.如果你可以上限显示(你应该能够); 然后选择VISIBLE_SHIFT成为上限的一小部分.

现在,"正确的答案"依赖于此,K让我们来探索挑选K.上面的"正确答案"说:

K是你选择的一个常数,这样你的计算的累积误差肯定是由最后的K个单位限定的(如果你不确定你的误差界限计算是正确的,那么使K比你的计算大几倍)说它应该是)

所以我们需要K.如果获取K比选择我更困难,更不直观,VISIBLE_SHIFT那么你将决定什么对你有用.要找到K我们要编写一个测试程序来查看一堆K值,以便我们可以看到它的行为方式.K如果'正确答案'可用,应该明白如何选择.没有?

我们将使用,作为"正确答案"的详细信息:

if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
Run Code Online (Sandbox Code Playgroud)

让我们试试K的所有值:

#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\n");
      else
        printf ("NO\n");
    }
}
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
Run Code Online (Sandbox Code Playgroud)

啊,如果我希望1e-13为'零',那么K应该是1e16或更大.

所以,我想说你有两个选择:

  1. 正如我所建议的那样,使用你的工程判断对'epsilon'的值进行简单的epsilon计算.如果你正在做图形和"零"意味着是一个"可见的变化",而不是检查你的视觉资产(图像等),并判断epsilon可以是什么.
  2. 在您阅读非货物主题答案的参考(并在此过程中获得博士学位)之后,不要尝试任何浮点计算,然后使用您的非直观判断进行选择K.

  • 解决方案独立性的一个方面是,您无法确定编译时"可见转换"是什么.在超级高清屏幕上看不见的东西在微小屏幕上可能非常明显.至少应该使它成为屏幕尺寸的函数.或者将其命名为其他东西. (10认同)
  • 但至少选择“可见偏移”是基于易于理解的显示(或框架)属性 - 与 &lt;正确答案的&gt; `K` 不同,后者难以选择且不直观。 (2认同)

Mic*_* T. 5

正确的问题:如何在Cocoa Touch中比较积分?

正确答案:CGPointEqualToPoint().

一个不同的问题:两个计算值是否相同?

答案贴在这里:他们不是.

如何检查它们是否接近?如果要检查它们是否接近,则不要使用CGPointEqualToPoint().但是,不要检查它们是否接近.做一些在现实世界中有意义的事情,比如检查一个点是否超出一条线或一个点是否在一个球体内.