Answers:
首先,浮点值的行为不是“随机的”。精确的比较可以并且确实在大量实际使用中有意义。但是,如果您要使用浮点,则需要知道它是如何工作的。假设浮点运算像实数一样容易出错,这会使您的代码快速中断。假设浮点结果与它们相关联时有很大的随机模糊(如此处的大多数答案所示),这样做会出错,使您的代码起初看起来可以工作,但最终会出现大幅度错误和断角情况。
首先,如果要使用浮点编程,则应阅读以下内容:
是的,请阅读所有内容。如果这太麻烦了,则应在计算之前使用整数/不动点进行计算。:-)
如此说来,精确浮点比较的最大问题归结为:
该地段价值的,你可以在源写,或与读取的事实scanf
或者strtod
,不存在浮动点值并获得静悄悄地转换为最接近的近似。这就是demon9733的答案。
由于没有足够的精度来表示实际结果,因此许多结果会四舍五入。一个简单的示例,您可以看到这是添加x = 0x1fffffe
和y = 1
浮动。在这里,x
尾数具有24位精度(可以),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检查应如下所示:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
当FLT_EPSILON
从定float.h
(含更换DBL_EPSILON
为double
S或LDBL_EPSILON
为long double
S)和K
是您选择这样的计算的累积误差绝对是以下所界定的恒定K
在最后的地方单位(如果你不知道你得到了错误绑定计算权,K
比您的计算结果要大几倍)。
最后,请注意,如果使用此选项,则可能需要特别注意零附近的值,因为FLT_EPSILON
这对于异常值没有意义。一个快速的解决方法是:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
DBL_MIN
如果使用双打,同样也要替换。
x
和y
具有不同的符号,那没有问题。右侧将“太小”,但是由于x
和y
具有不同的符号,因此它们无论如何都不应该相等。(除非它们是如此之小以至于不正常,但随后第二种情况抓住了它)
由于0可以精确地表示为IEEE754浮点数(或使用我曾经使用过的fp数的任何其他实现),所以与0进行比较可能是安全的。但是,如果程序计算出一个值(例如theView.frame.origin.x
),您可能会被咬住,但您有理由认为该值应为0,但您的计算不能保证为0。
为了澄清一点,计算如下:
areal = 0.0
将(除非您的语言或系统已损坏)创建一个值,以使(areal == 0.0)返回true,但另一个计算如
areal = 1.386 - 2.1*(0.66)
不得。
如果您可以确保自己的计算得出的值为0(而不仅是它们得出的值应该为0),则可以继续将fp值与0进行比较。如果不能保证自己达到所需的程度, ,最好坚持“容忍平等”的通常做法。
在最坏的情况下,粗心地比较fp值可能会非常危险:考虑航空电子设备,武器制导,电厂操作,车辆导航,以及几乎任何能够满足实际情况的应用。
对于愤怒的小鸟,没有那么危险。
1.30 - 2*(0.65)
是一个表达式的完美示例,如果您的编译器实现IEEE 754,则该表达式的求值显然为0.0,因为以0.65
和表示的双精度数1.30
具有相同的有效位数,并且乘以2显然很精确。
我想给出一个与其他人不同的答案。它们非常适合回答您陈述的问题,但可能不适用于您需要了解的内容或真正的问题。
图形中的浮点很好!但是几乎不需要直接比较浮点数。您为什么需要这样做?图形使用浮点数来定义间隔。并且比较浮点数是否也在由浮点数定义的间隔内也总是定义明确的,仅需保持一致,不准确或不精确!只要可以分配所有图像所需的像素(也是一个间隔!)。
因此,如果要测试点是否在[0..width [范围外],就可以了。只要确保您一致地定义包含即可。例如,始终将内部定义为(x> = 0 && x <width)。相交或命中测试也是如此。
但是,如果您滥用图形坐标作为某种标志,例如查看窗口是否停靠,则不应这样做。请使用与图形表示层分开的布尔标志。
只要零不是一个计算值,比较零即可是安全的操作(如以上答案中所述)。原因是零是浮点数中可完美表示的数字。
如果说出完美可表示的值,则可以得到2的幂(单精度)概念中的24位范围。因此,1、2、4是完全可表示的,.5,.25和.125也是。只要您所有的重要位都在24位中,您就是黄金。因此,可以精确表示10.625。
这很好,但是在压力下会很快崩溃。我想到了两种情况:1)涉及计算时。不要相信sqrt(3)* sqrt(3)==3。那不是那样的。正如其他答案所暗示的那样,它可能不在epsilon之内。2)当涉及任何非2的幂(NPOT)时。因此,听起来可能很奇怪,但是0.1是二进制中的一个无穷级数,因此任何涉及此类数字的计算从一开始都是不精确的。
(哦,最初的问题提到了与零的比较。别忘了-0.0也是一个完全有效的浮点值。)
[“正确答案”掩盖了选择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比计算值大几倍)说应该)
所以我们需要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
。K
,后者很难选择且不直观。
我上次检查C标准时,没有要求对双精度(总计64位,53位尾数)进行浮点运算,以达到比该精度更高的精度。但是,某些硬件可能会在精度更高的寄存器中执行操作,并且该要求被解释为无需清除低序位(超出装入寄存器的数字的精度)。这样,您可能会得到这样意想不到的比较结果,具体取决于最后一次睡觉的人在寄存器中留下的内容。
就是说,尽管我努力在每次看到它时都将其删除,但是我工作的公司还是有许多使用gcc编译并在linux上运行的C代码,而且很长一段时间我们都没有注意到这些意外结果。我不知道这是否是因为gcc正在为我们清除低序位,80位寄存器未用于现代计算机上的这些操作,标准已更改或其他原因。我想知道是否有人可以引用章节和经文。
您可以使用以下代码将浮点数与零进行比较:
if ((int)(theView.frame.origin.x * 100) == 0) {
// do important operation
}
这将与0.1精度进行比较,在这种情况下,对于CGFloat而言已经足够。
int
没有保证的情况下强制转换theView.frame.origin.x
为int
导致不确定行为(UB)的范围-在这种情况下,即的范围的1/100 int
。
-(BOOL)isFloatEqual:(CGFloat)firstValue secondValue:(CGFloat)secondValue{
BOOL isEqual = NO;
NSNumber *firstValueNumber = [NSNumber numberWithDouble:firstValue];
NSNumber *secondValueNumber = [NSNumber numberWithDouble:secondValue];
isEqual = [firstValueNumber isEqualToNumber:secondValueNumber];
return isEqual;
}
我正在使用以下比较功能来比较小数位数:
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
}
我会说正确的事情是将每个数字声明为一个对象,然后在该对象中定义三件事:1)一个相等运算符。2)一个setAcceptableDifference方法。3)价值本身。如果两个值的绝对差小于设置为可接受的值,则相等运算符返回true。
您可以子类化对象以适合该问题。例如,如果直径在1-2英寸之间的金属圆棒的直径差异小于0.0001英寸,则可以认为它们相等。因此,您可以使用参数0.0001调用setAcceptableDifference,然后放心使用相等运算符。
fabs(x+y)
如果x
和y
(可以)具有不同的符号是有问题的。仍然,这是对“货色”比较大潮的一个很好的答案。