我知道UIKit使用CGFloat,因为它是分辨率无关的坐标系。
但每次我想检查frame.origin.x是否为0时,我都觉得很恶心:
if (theView.frame.origin.x == 0) {
// do important operation
}
当与==,<=,>=,<,>比较时,CGFloat是否容易出现假阳性?
它是一个浮点数,它们有不精确的问题:例如0.0000000000041。
Objective-C在比较时是否会在内部处理这个或者是否会发生原点。读作0的X不与0比较为真?
因为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值可能是极其危险的:想想航空电子设备、武器制导、发电厂操作、车辆导航,几乎所有计算与现实世界相结合的应用。
对《愤怒的小鸟》来说,没有那么危险。
我想给出一个和其他人不一样的答案。他们很好地回答了你的问题,但可能不是你需要知道的或你真正的问题是什么。
图形中的浮点数很好!但是几乎没有必要直接比较浮点数。你为什么要这么做?图形使用浮点数来定义间隔。比较浮动是否在浮动所定义的区间内总是定义良好的,只需要保持一致,而不需要精确或精确!只要可以分配一个像素(这也是一个间隔!),这就是所有的图形需求。
所以如果你想测试你的点是否在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。
另一个可能需要记住的问题是,不同的实现做事情的方式不同。我非常熟悉的一个例子是索尼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是索尼公司的注册商标。