比较Objective-C中的浮点数的奇怪问题

时间:2009-10-23 16:25:52

标签: iphone objective-c casting floating-point

在算法的某个时刻,我需要将类的属性的浮点值与浮点数进行比较。所以我这样做:

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?

感谢。

7 个答案:

答案 0 :(得分:30)

我相信,如果没有找到标准,那么在比较floatdouble时,float会在比较之前投放到double。没有修饰符的浮点数在C中被视为double

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

而不是使用(float)0.1,您可以使用0.1f来阅读更好的内容。

答案 1 :(得分:6)

问题在于,正如您在问题中所建议的那样,您正在将浮动与双重进行比较。

比较浮点数存在一个更普遍的问题,这是因为当您对浮点数进行计算时,计算结果可能与您预期的不完全相同。产生的浮点数的最后一位是错误的(虽然不准确度可能大于最后一位),这是相当普遍的。如果使用==来比较两个浮点数,那么浮点数必须相同才能使浮点数相等。如果你的计算结果略微不准确,那么他们就不会在你期望的时候进行比较。您可以比较它们,看它们是否几乎相等,而不是比较这样的值。为此,您可以获取浮点数之间的正差异,并查看它是否小于给定值(称为epsilon)。

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

FLT_EPSILON是1和下一个最接近的浮点数之间的差距。这意味着如果您的数字介于1和2之间,那么选择它可能是一个好的epsilon,但如果您的值大于2,则使用此epsilon是没有意义的,因为2和下一个最接近的float之间的差距大于epsilon。所以我们必须选择相对于浮点数大小的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函数。

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

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

答案 2 :(得分:4)

双精度和浮点数对于二进制的尾数存储具有不同的值(浮点数是23位,双精度54)。这些几乎永远不会是平等的。

The IEEE Float Point article on wikipedia可以帮助您理解这种区别。

答案 3 :(得分:4)

在C中,像0.1这样的浮点字面值是double,而不是float。由于被比较的数据项的类型不同,因此比较以更精确的类型(双)进行。在我所知的所有实现中,float具有比double更短的表示(通常表示为6比较小数位的14位)。此外,算术是二进制的,而1/10没有二进制的精确表示。

因此,你正在使用一个浮点数0.1,它会失去准确性,将其扩展到两倍,并且期望它比较等于双0.1,这会降低精度。

假设我们以十进制形式执行此操作,浮点数为三位数,双精度数为六位,我们将比较为1/3。

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

答案 4 :(得分:4)

0.1实际上是存储二进制文件非常困难的值。在基数2中,1/10是无限重复的分数

0.0001100110011001100110011001100110011001100110011...

正如有几位人士指出的那样,必须用完全相同精度的常数进行比较。

答案 5 :(得分:1)

通常,在任何语言中,您都不能真正依赖浮点类型的相等性。在你的情况下,因为它看起来你有更多的控制,它确实看起来默认情况下0.1不浮动。您可能会发现sizeof(0.1)(与sizeof(self.scroller.currentValue)相对应。

答案 6 :(得分:-1)

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

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

return [numberA isEqualToString: numberB];