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

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

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

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

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


当前回答

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

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

其他回答

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

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

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

我使用下面的比较函数来比较小数点后的数位:

bool compare(const double value1, const double value2, const int precision)
{
    int64_t magnitude = static_cast<int64_t>(std::pow(10, precision));
    int64_t intValue1 = static_cast<int64_t>(value1 * magnitude);
    int64_t intValue2 = static_cast<int64_t>(value2 * magnitude);
    return intValue1 == intValue2;
}

// Compare 9 decimal places:
if (compare(theView.frame.origin.x, 0, 9)) {
    // do important operation
}

因为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值可能是极其危险的:想想航空电子设备、武器制导、发电厂操作、车辆导航,几乎所有计算与现实世界相结合的应用。

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

你可以使用这样的代码来比较float和0:

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

这将与0.1的精度进行比较,在这种情况下,这对CGFloat来说足够了。