我知道UIKit
因使用独立于分辨率的坐标系而使用CGFloat
。
但每次我想检查例如frame.origin.x
是0
是否让我感到恶心:
if (theView.frame.origin.x == 0) {
// do important operation
}
与CGFloat
,==
,<=
,>=
,<
进行比较时,>
是否容易受到误报?
它是一个浮点,它们有不准确的问题:例如0.0000000000041
。
比较时Objective-C
是否在内部处理此问题,或者读取为零的origin.x
与0
的比较是否为真?
答案 0 :(得分:450)
首先,浮点值的行为并非“随机”。在大量现实世界的用法中,精确的比较可以而且确实有意义。但是如果你要使用浮点数,你需要知道它是如何工作的。假设浮点工作就像实数一样,会让你快速破解代码。假设浮点结果与它们相关的大量随机模糊(就像这里建议的大多数答案一样)会让你看起来首先工作的代码,但最终会产生大幅度的错误和破坏的角落情况。
首先,如果你想用浮点编程,你应该读这个:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
是的,阅读全部内容。如果这是一个太大的负担,你应该使用整数/固定点进行计算,直到你有时间阅读它。 : - )
现在,据说,精确浮点比较的最大问题归结为:
您可以在源代码中编写或使用scanf
或strtod
,读取的大量值不存在作为浮点值,静默地转换为最接近的近似值。这就是demon9733的答案所说的。
由于没有足够的精度来表示实际结果,许多结果都会被舍入。您可以看到这一点的简单示例是将x = 0x1fffffe
和y = 1
添加为浮点数。这里,x
在尾数中有24位精度(ok),而y
只有1位,但是当你添加它们时,它们的位不在重叠位置,结果需要25位精度相反,它会四舍五入(在默认的舍入模式下为0x2000000
。)
由于需要无限多的位置来获得正确的值,许多结果都会被舍入。这包括合理的结果,如1/3(你从十进制熟悉,它需要无限多的地方),但也是1/10(这也是二进制中无限多的地方,因为5不是2的幂),以及不合理的结果,如任何不完美正方形的平方根。
双舍入。在某些系统(特别是x86)上,浮点表达式的精度高于其标称类型。这意味着当上述类型的舍入之一发生时,您将获得两个舍入步骤,首先将结果舍入到更高精度类型,然后舍入到最终类型。例如,如果将1.49舍入为整数(1),请考虑以十进制发生的情况,而如果首先将其舍入到一个小数位(1.5),然后将结果舍入为整数(2),则会发生什么。这实际上是浮点处理的最糟糕的区域之一,因为编译器的行为(特别是对于有缺陷的,不符合GCC的编译器)是不可预测的。
超越函数(trig
,exp
,log
等)未指定具有正确的舍入结果;结果在最后一个精度位置(通常称为 1ulp )的一个单位内被指定为正确。
当您编写浮点代码时,您需要记住您正在对可能导致结果不准确的数字执行的操作,并相应地进行比较。通常,与“epsilon”进行比较是有意义的,但是epsilon应该基于您正在比较的数字的幅度,而不是绝对常数。 (如果绝对常数epsilon可以工作,那就强烈表明固定点,而不是浮点,是工作的正确工具!)
编辑:特别是,幅度相对的epsilon检查应该类似于:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
其中FLT_EPSILON
是来自float.h
的常量(替换为DBL_EPSILON
的{{1}}或double
的{{1}}和{ {1}}是你选择的一个常数,这样你的计算的累积误差肯定会被最后一个LDBL_EPSILON
个单位所限制(如果你不确定你得到了错误绑定计算权,那么make {{ 1}}比你的计算所说的要大几倍。
最后请注意,如果你使用它,可能需要在零附近做一些特别小心,因为long double
对于非正规数没有意义。快速解决方法就是:
K
如果使用双打,同样替换K
。
答案 1 :(得分:34)
由于0可以完全表示为IEEE754浮点数(或使用我曾经使用的任何其他f-p数字实现),因此与0进行比较可能是安全的。但是,如果你的程序计算了一个你有理由认为应该为0但你的计算不能保证为0的值(例如theView.frame.origin.x
),你可能会被咬伤。
稍微澄清,计算如:
areal = 0.0
将(除非您的语言或系统被破坏)创建一个值,使得(areal == 0.0)返回true但是另一个计算,例如
areal = 1.386 - 2.1*(0.66)
可能没有。
如果你可以向自己保证你的计算产生0的值(而不仅仅是它们产生的值应该是0)那么你可以继续将fp值与0进行比较。如果你不能保证自己所需的学位,最好坚持“宽容平等”的通常方法。
在最糟糕的情况下,对f-p值的粗心比较可能是非常危险的:想想航空电子设备,武器引导,发电厂运行,车辆导航,几乎任何计算符合现实世界的应用。
对于愤怒的小鸟,没那么危险。
答案 2 :(得分:20)
我想给出一些与其他答案不同的答案。它们非常适合回答您所述的问题,但可能不适合您需要了解的内容或您的真正问题。
图形中的浮点很好!但是几乎没有必要直接比较浮子。你为什么要这么做? Graphics使用浮点数来定义间隔。并且比较浮点是否也在浮点数定义的区间内总是很明确,只需要一致,不准确或精确!只要一个像素(也是一个间隔!)可以分配所有图形需要。
所以,如果你想测试你的点是否在[0..width [范围之外]这是好的。只需确保一致地定义包含。例如,始终定义内部是(x> = 0&amp;&amp; x&lt; width)。交叉点或命中测试也是如此。
但是,如果您将图形坐标滥用为某种标记,例如查看窗口是否已停靠,则不应执行此操作。请使用与图形表示层分开的布尔标志。
答案 3 :(得分: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也是一个完全有效的浮点值。)
答案 4 :(得分:10)
['正确答案'掩盖了选择K
。选择K
最终会像选择VISIBLE_SHIFT
一样临时,但选择K
不太明显,因为与VISIBLE_SHIFT
不同,它不基于任何显示属性。因此,选择你的毒药 - 选择K
或选择VISIBLE_SHIFT
。这个答案提倡选择VISIBLE_SHIFT
,然后展示选择K
]
正是由于圆形错误,您不应该使用'exact'值进行逻辑运算的比较。在您在视觉显示器上的位置的特定情况下,如果位置是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
啊,如果我希望1e-13为'零',那么K应该是1e16或更大。
所以,我想说你有两个选择:
K
答案 5 :(得分:5)
正确的问题:如何比较Cocoa Touch中的点数?
正确答案:CGPointEqualToPoint()。
另一个问题:两个计算值是否相同?
答案在这里张贴:他们不是。
如何检查它们是否接近?如果要检查它们是否接近,则不要使用CGPointEqualToPoint()。但是,不要检查它们是否接近。做一些在现实世界中有意义的事情,比如检查一个点是否超出一条线或一个点是否在一个球体内。
答案 6 :(得分:4)
我最后一次检查C标准时,没有要求对双精度(总共64位,53位尾数)的浮点运算精确到超过该精度。但是,某些硬件可能会在更高精度的寄存器中执行操作,并且该要求被解释为不需要清除低阶位(超出加载到寄存器中的数字的精度)。所以你可能会得到意想不到的比较结果,这取决于最后睡觉的人在寄存器中留下的内容。
尽管如此,尽管我每次看到它都会努力消除它,但我工作的服装有很多C代码,它们是用gcc编译的并在linux上运行,我们没有发现任何这些意外的结果很长时间。我不知道这是因为gcc正在为我们清除低位,80位寄存器不能用于现代计算机上的这些操作,标准已经改变,或者是什么。我想知道是否有人可以引用章节和诗句。
答案 7 :(得分:1)
您可以使用此类代码将float浮动为零:
if ((int)(theView.frame.origin.x * 100) == 0) {
// do important operation
}
这将与0.1准确度进行比较,在这种情况下对于CGFloat来说足够了。
答案 8 :(得分:-1)
我正在使用以下比较功能来比较小数位数:
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
}
答案 9 :(得分:-6)
我说正确的做法是将每个数字声明为一个对象,然后在该对象中定义三个东西:1)一个相等运算符。 2)setAcceptableDifference方法。 3)价值本身。如果两个值的绝对差值小于设置为可接受的值,则等于运算符返回true。
您可以将对象子类化以适应问题。例如,如果它们的直径相差小于0.0001英寸,则认为1到2英寸之间的金属圆棒具有相同的直径。所以你用参数0.0001调用setAcceptableDifference,然后放心地使用相等运算符。