2009-10-23 71 views
23

在算法中的某个点,我需要比较一个类的属性的浮动值与浮动。所以我这样做:奇怪的问题比较目标中的浮动C

if (self.scroller.currentValue <= 0.1) { 
} 

其中currentValue是一个浮点属性。

然而,当我有平等和self.scroller.currentValue = 0.1 if语句不满足,代码不执行!我发现我可以通过投0.1来解决这个问题。像这样:

if (self.scroller.currentValue <= (float)0.1) { 
} 

这工作正常。

任何人都可以解释为什么发生这种情况? 0.1被默认定义为double还是其他东西?

谢谢。

+4

另请参见:“什么每台计算机科学家应该知道关于浮点运算:” http://docs.sun.com/source/ 806-3568/ncg_goldberg.html – 2009-10-23 19:08:53

+0

对于那些努力改善我的答案的你(特别是@alastair),我不相信它可以得到改善。我同意这是不正确的,可能很危险。我删除了它。请参阅James Snook的答案,对这个非平凡的问题进行更深入的探索。 – 2016-09-26 17:08:14

+0

@RobNapier我不得不说,我认为值得保留答案并附加更正,但我理解你的观点。道歉,如果我的编辑看起来有点侵略性 - 我只是想说清楚问题所在。 – alastair 2016-09-27 13:38:21

回答

30

我相信,具有不发现是这么说的,在比较时floatdoublefloat之前转换为double标准比较没有修饰符的浮点数在C中被认为是double

但是,在C中,在浮点数和双精度中没有0.1的精确表示。现在,使用浮点数会给你一个小错误。使用double会给你一个更小的错误。现在的问题是,通过将float转换为double,您可以继续执行float的更大误差。当然,他们现在并没有走得比较平等。

而不是使用(float)0.1你可以使用0.1f这是一个有点更好看的。

1

一般来说,在任何语言,你真的不能指望浮动样类型的平等。在你的情况,因为它看起来像你有更多的控制,它看起来0.1默认情况下不浮动。你可能会发现sizeof(0.1)(vs. sizeof(self.scroller.currentValue)

+0

这是一个好主意!我没有想到sizeof() – Dimitris 2009-10-23 16:33:35

+0

sizeof显示0.1是一个double。如果两者的价值都是0.10000,那么你是不是平等呢? – Dimitris 2009-10-23 16:39:01

+0

@Dimitris,这并不奇怪。请参阅@ MarkPowell的答案。 – 2009-10-23 16:59:46

4

在C中,浮点文字状0.1是双,而不是一个浮子。由于被比较的数据项的类型不同,所以比较以更精确的类型(双)进行。在我所知道的所有实现中,float的表示比double更短(通常表示为6对14位小数)。此外,算法是二进制的,1/10在二进制中没有确切的表示。

因此,你正在做一个浮动0.1,它失去精度,延长它的两倍,并期待它比等于一个双0.1,它失去精度较差。

假设十进制是三位数,而且双打6我们这样做,有浮动,而我们比较到1/3。

我们存储的浮点值是0.333。我们将它与价值为0.333333的双倍数进行比较。我们将浮点数0.333转换为双倍0.333000,并发现它不同。

+0

遵循这个十进制思想(它与二进制不同的硬数字,但概念是相同的),无论您选择多少(有限)数字,您都会发现(1/3)* 3!= 1。这就是为什么用float或double来计算所有数学并不能真正解决问题的原因。 – 2009-10-23 18:30:27

+0

对。当然,你可以通过使用足够的数字来任意地接近1,所以测试获得真正的接近效果要比测试平等更好。这里真正的问题是浮点相等性测试通常不起作用。 – 2009-10-23 20:00:06

+0

同意。因此,可用警告(我建议打开)以防止意外使用浮点相等。 – 2009-10-24 16:37:27

4

0.1实际上是一个非常难以存储二进制值。在基体2,是1/10无限重复分数

0.0001100110011001100110011001100110011001100110011... 

正如几个已经指出的那样,比较具有完全相同的精度恒定,以制成。

6

问题是,正如你在你的问题中所建议的那样,你正在比较一个float和double。

比较浮点数有一个更一般的问题,这是因为当您对浮点数进行计算时,计算结果可能并不完全符合您的预期。结果浮点数的最后一位会出错是很常见的(尽管不准确性可能比最后一位大)。如果使用==来比较两个浮点数,则所有的位必须相同,以使浮点数相等。如果你的计算结果稍微不准确,那么当你期望它们时,它们不会相等。不要比较这些值,你可以比较它们,看它们是否几乎相等。要做到这一点,你可以采取浮动之间的积极差异,看看它是否小于给定值(称为epsilon)。

要选择一个好的epsilon,你需要了解一些浮点数。浮点数的工作原理类似于将数字表示为给定数量的有效数字。如果我们工作到5位有效数字,并且结果的最后一位数字中的计算结果是错误的,那么1.2345的误差为+ -0.0001,而1234500的误差为+ -100。如果您始终将误差基准值设置为1.2345,那么比较例程将与所有大于10的值(使用小数点时)的==相同。这在二进制中更糟糕,它的值都大于2.这意味着我们选择的epsilon必须相对于我们正在比较的浮点数的大小。

FLT_EPSILON是1和下一个最接近的浮点之间的差距。这意味着如果你的数字在1和2之间,选择它可能是一个很好的选择,但是如果你的数值大于2,使用这个epsilon是毫无意义的,因为2和下一个最接近的浮点之间的差距大于epsilon。所以我们必须选择一个相对于我们花车大小的ε(因为计算中的误差与我们花车的大小有关)。

良好(ISH)浮点比较常规看起来是这样的:

bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier)  
{ 
    float epsilon; 
    /* May as well do the easy check first. */ 
    if (a == b) 
    return true; 

    if (a > b) { 
    epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier; 
    } else { 
    epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier; 
    } 

    return fabs (a - b) <= epsilon; 
} 

这种比较例行程序比较相对于通过的最大的浮法规模花车scalbnf(1.0f, ilogb(a)) * FLT_EPSILON发现a和未来之间的差距最近的浮动。然后乘以epsilonMultiplier,所以可以调整差异的大小,具体取决于计算结果可能有多不准确。

你可以做一个简单的compareLessThan程序是这样的:

bool compareLessThan (float a, float b, unsigned epsilonMultiplier) 
{ 
    if (compareNearlyEqual (a, b, epsilonMultiplier) 
    return false; 

    return a < b; 
} 

你也可以写一个非常类似的compareGreaterThan功能。

值得注意的是,比较像这样的花车可能并不总是你想要的。举例来说,这个函数永远不会发现float值接近于0,除非它为0.要解决这个问题,您需要确定您认为接近于零的值,并为此编写一个额外的测试。

有时您获得的不准确性将不取决于计算结果的大小,而是取决于您计算的值。例如sin(1.0f + (float)(200 * M_PI))的结果将比sin(1.0f)的结果少得多(结果应该相同)。在这种情况下,您的比较例程必须查看您计算的数字,以了解答案的误差范围。

-1

将其转换为字符串,然后比较:

NSString* numberA = [NSString stringWithFormat:@"%.6f", a]; 
NSString* numberB = [NSString stringWithFormat:@"%.6f", b]; 

return [numberA isEqualToString: numberB];