我知道UIKit使用CGFloat,因为它是分辨率无关的坐标系。

但每次我想检查frame.origin.x是否为0时,我都觉得很恶心:

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

当与==,<=,>=,<,>比较时,CGFloat是否容易出现假阳性? 它是一个浮点数,它们有不精确的问题:例如0.0000000000041。

Objective-C在比较时是否会在内部处理这个或者是否会发生原点。读作0的X不与0比较为真?


当前回答

另一个可能需要记住的问题是,不同的实现做事情的方式不同。我非常熟悉的一个例子是索尼Playstation 2上的FP单元。与任何X86设备中的IEEE FP硬件相比,它们有显著的差异。引用的文章提到完全缺乏对inf和NaN的支持,而且情况变得更糟。

不太为人所知的是我后来所知道的“一位乘法”错误。对于float x的特定值:

    y = x * 1.0;
    assert(y == x);

断言将失败。在一般情况下,有时,但不总是,在Playstation 2上FP相乘的结果比等效的IEEE尾数少一位。

我的观点是,您不应该假设将FP代码从一个平台移植到另一个平台会产生相同的结果。任何给定的平台都是内部一致的,因为结果在该平台上不会改变,只是它们可能与另一个平台不一致。例如,X86上的CPython使用64位双精度来表示浮点数,而Cortex MO上的CircuitPython必须使用软件FP,并且只使用32位浮点数。不用说,这会引起差异。

我40多年前学到的一句话今天依然适用。“在计算机上做浮点运算就像移动一堆沙子。每次你做任何事,都会留下一点沙子,捡起一点泥土。”

Playstation是索尼公司的注册商标。

其他回答

另一个可能需要记住的问题是,不同的实现做事情的方式不同。我非常熟悉的一个例子是索尼Playstation 2上的FP单元。与任何X86设备中的IEEE FP硬件相比,它们有显著的差异。引用的文章提到完全缺乏对inf和NaN的支持,而且情况变得更糟。

不太为人所知的是我后来所知道的“一位乘法”错误。对于float x的特定值:

    y = x * 1.0;
    assert(y == x);

断言将失败。在一般情况下,有时,但不总是,在Playstation 2上FP相乘的结果比等效的IEEE尾数少一位。

我的观点是,您不应该假设将FP代码从一个平台移植到另一个平台会产生相同的结果。任何给定的平台都是内部一致的,因为结果在该平台上不会改变,只是它们可能与另一个平台不一致。例如,X86上的CPython使用64位双精度来表示浮点数,而Cortex MO上的CircuitPython必须使用软件FP,并且只使用32位浮点数。不用说,这会引起差异。

我40多年前学到的一句话今天依然适用。“在计算机上做浮点运算就像移动一堆沙子。每次你做任何事,都会留下一点沙子,捡起一点泥土。”

Playstation是索尼公司的注册商标。

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

您可以对对象进行子类化以适应该问题。例如,如果1到2英寸之间的圆金属棒的直径相差小于0.0001英寸,则可以认为它们的直径相等。因此,您可以使用参数0.0001调用setAcceptableDifference,然后放心地使用相等操作符。

我想给出一个和其他人不一样的答案。他们很好地回答了你的问题,但可能不是你需要知道的或你真正的问题是什么。

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

所以如果你想测试你的点是否在a [0..]宽度[范围,这很好。只要确保你对包含的定义是一致的。例如,总是定义内部是(x>=0 && x < width)。这同样适用于交叉测试或命中测试。

但是,如果您滥用图形坐标作为某种标志,例如查看窗口是否停靠,则不应该这样做。使用一个独立于图形表示层的布尔标志。

[“正确答案”掩盖了选择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 <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

啊,所以K应该是1e16或者更大如果我想让1e13等于0。

所以,我认为你有两个选择:

就像我建议的那样,用你的工程判断来做一个简单的计算。如果你做的是图形,“零”意味着“可见的变化”,那就检查你的视觉资产(图像等)并判断epsilon可以是什么。 不要尝试任何浮点计算,直到你阅读了非货物崇拜答案的参考资料(并在此过程中获得博士学位),然后使用你的非直觉判断选择K。

因为0完全可以表示为一个IEEE754浮点数(或者使用我曾经使用过的任何其他f-p数字实现),与0比较可能是安全的。然而,如果你的程序计算了一个值(比如theView.frame.origin.x),你有理由相信它应该是0,但你的计算不能保证它是0,你可能会被咬。

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

areal = 0.0

会(除非你的语言或系统坏了)创建一个值(areal==0.0)返回true,但另一个计算,如

areal = 1.386 - 2.1*(0.66)

可能不会。

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

在最坏的情况下,不小心比较f-p值可能是极其危险的:想想航空电子设备、武器制导、发电厂操作、车辆导航,几乎所有计算与现实世界相结合的应用。

对《愤怒的小鸟》来说,没有那么危险。