提问



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


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


if (theView.frame.origin.x == 0) {
    // do important operation
}


==<=>=<>相比,是否[[t CGFloat容易受到假阳性的影响?
这是一个浮点,他们有不寻常的问题:0.0000000000041例如。


Objective-C在比较时是否在内部处理这个问题,或者读取为零的origin.x0的比较是否为真?

最佳参考


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


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


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


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


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



  1. 您可以在源中写入或使用scanfstrtod,读取的大量值不存在作为浮点值并进行静默转换到最接近的近似值。这就是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. 超越函数(trigexplog等)未指定具有正确的舍入结果;结果只是在最后一个精度位置(通常称为 1ulp )的一个单位内指定正确。



当你编写浮点代码时,你需要记住你正在用可能导致结果不精确的数字做什么,并相应地进行比较。通常,与epsilon进行比较是有意义的,但是epsilon应该基于您正在比较的数字的幅度,而不是绝对常数。 (如果绝对常数epsilon可以工作,那就强烈表明固定点,而不是浮点,是工作的正确工具!)


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


if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))


其中FLT_EPSILONfloat.h的常数(用double代替DBL_EPSILONlong double代替LDBL_EPSILONK]]是你选择的一个常数,这样你的计算的累积误差肯定是由K单位在最后一个地方限定的(如果你不确定你得到错误约束计算权,请K比你的计算所说的要大几倍。)


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


if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)


如果使用双打,同样替代DBL_MIN

其它参考1


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


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


areal = 0.0


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


areal = 1.386 - 2.1*(0.66)


不得。


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


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


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

其它参考2


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


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


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


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

其它参考3


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


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


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


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

其它参考4


[[正确答案掩盖了选择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) { /* ... */ }


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


现在,正确答案依赖于K所以让我们探索挑选K。上面的正确答案说:



??K是你选择的常数,这样你的累积误差
??计算肯定受到最后K个单位的限制(和
??如果你不确定你是否有错误约束计算权利,那就让K a
??比你的计算所说的要大几倍



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


我们将使用,作为正确答案的细节:


if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)


让我们试试K的所有值:


#include 
#include 
#include 

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


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


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



  1. 使用 工程判断 对epsilon的值进行简单的epsilon计算,正如我所建议的那样。如果你正在做图形并且零是意味着是一个可见的变化,而不是检查你的视觉资产(图像等),并判断epsilon是什么。

  2. 不要尝试任何浮点计算,直到你已经阅读了非货物崇拜答案的参考(并在此过程中获得了博士学位),然后使用你的非直觉判断来选择K]]


其它参考5


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


正确答案:CGPointEqualToPoint()。


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


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


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

其它参考6


我最后一次检查C标准时,没有要求对双精度(总共64位,53位尾数)的浮点运算精确到超过该精度。但是,某些硬件可能会在更高精度的寄存器中执行操作,并且该要求被解释为不需要清除低阶位(超出加载到寄存器中的数字的精度)。所以你可能会得到意想不到的比较结果,这取决于最后睡觉的人在寄存器中留下的内容。


也就是说,尽管我每次看到它都会努力消除它,但我工作的装备有很多C代码,它们是用gcc编译的,并且在linux上运行,我们在很长一段时间内都没有注意到这些意外的结果。我不知道这是因为gcc是为我们清除低位,80位寄存器不是用于现代计算机上的这些操作,标准已经改变,或者是什么。我想知道是否有人可以引用章节和诗句。

其它参考7


您可以使用此类代码将float与零进行比较:


if ((int)(theView.frame.origin.x * 100) == 0) {
    // do important operation
}


这将与0.1准确度进行比较,在这种情况下足以用于CGFloat。

其它参考8


我说正确的事情是将每个数字声明为一个对象,然后在该对象中定义三个东西:1)一个相等运算符.2)一个setAcceptableDifference方法.3)值本身。如果是,则相等运算符返回true两个值的绝对差值小于设定为可接受的值。


您可以将对象子类化以适应问题。例如,如果它们的直径相差小于0.0001英寸,则认为1到2英寸之间的金属圆棒具有相同的直径。因此,您可以使用参数0.0001调用setAcceptableDifference,然后自信地使用相等运算符。