【问题标题】:Strange problem comparing floats in objective-C比较objective-C中的浮点数的奇怪问题
【发布时间】:2010-12-09 12:42:06
【问题描述】:

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

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 还是什么?

谢谢。

【问题讨论】:

  • 另请参阅“每位计算机科学家应了解的浮点运算知识:”docs.sun.com/source/806-3568/ncg_goldberg.html
  • 对于那些努力改进我的答案的人(尤其是@alastair),我不相信它可以改进。我同意这是不正确的,可能很危险。我已经删除了。请参阅 James Snook 的回答,以更深入地探索这个不平凡的问题。
  • @RobNapier 我不得不说我认为保留答案是值得的,并添加了更正,但我理解您对此的看法。如果我的编辑看起来有点咄咄逼人,我深表歉意——我只是想弄清楚问题所在。
  • @alastair 一点也不;它帮助摆脱了我认为误导人们(包括我自己)的东西。我宁愿用您想添加的任何内容来改进 James Snook 的答案。 SO 上的错误,即使 标记 错误,有时也会让读者感到困惑。如果您的代表不允许您查看已删除的帖子,并且您想将以前写的内容复制到您自己的答案或 James 的答案中,我已在此处发布旧文本(也适用于任何好奇我们在谈论什么的人:D ): gist.github.com/rnapier/78502480e53f526d24f30a14032dea8d)

标签: iphone objective-c casting floating-point


【解决方案1】:

我相信,在没有找到这样说的标准时,在比较 floatdouble 时,float 在比较之前被转换为 double。没有修饰符的浮点数在 C 中被认为是 double

但是,在 C 中,浮点数和双精度数中没有 0.1 的精确表示。现在,使用浮点数会给您带来一个小错误。使用 double 会给你一个更小的错误。现在的问题是,通过将float 转换为double,您会继承float 的较大错误。当然,他们现在已经不一样了。

您可以使用0.1f,而不是使用(float)0.1,这样更易​​于阅读。

【讨论】:

    【解决方案2】:

    问题是,正如您在问题中所建议的那样,您将浮点数与双精度数进行比较。

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

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

    FLT_EPSILON 是 1 和下一个最近的浮点数之间的差距。这意味着如果您的数字介于 1 和 2 之间,选择 epsilon 可能是一个不错的选择,但如果您的值大于 2,则使用此 epsilon 是没有意义的,因为 2 和下一个最近的浮点数之间的差距大于 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) 准确得多(结果应该相同)。在这种情况下,您的比较例程必须查看您在计算中输入的数字才能知道答案的误差范围。

    【讨论】:

    • 什么是epsilonMultiplier
    • @AlbertRenshaw epsilonMultiplier 允许您调整 epsilon 的大小。例如,将其设置为1 允许最接近所需结果的两个浮点数计为相等。增加它会增加比较相等的值的数量。您可以根据值的计算方式或实验来推断epsilonMultiplier 的大小。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      因此,您采用浮点 0.1,它会失去准确性,将其扩展为双精度,并期望它与双精度 0.1 进行比较,从而降低精度。

      假设我们以十进制执行此操作,float 为三位,double 为六,我们与 1/3 进行比较。

      我们存储的浮点值为 0.333。我们将其与值为 0.333333 的双精度数进行比较。我们把float 0.333转换成double 0.333000,发现不一样了。

      【讨论】:

      • 按照十进制的想法(与二进制有不同的硬数,但概念相同),你会发现 (1/3)*3 != 1,无论多少(有限) 您选择的数字。这就是为什么在 float 或 double 中进行所有数学运算并不能真正解决问题的原因。
      • 对。当然,您可以通过使用足够多的数字来任意接近 1,因此测试非常接近比测试相等要好得多。这里真正的问题是浮点相等性测试在一般情况下不起作用。
      • 同意。因此,可用的警告(我建议打开)以防止您意外使用浮点相等。
      【解决方案5】:

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

      0.0001100110011001100110011001100110011001100110011...
      

      正如一些人指出的那样,比较必须使用精度完全相同的常数。

      【讨论】:

        【解决方案6】:

        通常,在任何语言中,您都不能真正指望类浮点类型的相等性。在您的情况下,由于看起来您拥有更多控制权,因此默认情况下 0.1 似乎不是浮动的。您可能会发现 sizeof(0.1) (与 sizeof(self.scroller.currentValue) 相比)。

        【讨论】:

        • sizeof 表明 0.1 是双精度数。当两者的值都为 0.10000 时,你没有得到相等仍然很奇怪,不是吗?
        • @Dimitris,这并不奇怪。请参阅@MarkPowell 的回答。
        • @Dimitris:不,这不是很奇怪。 0.1 不能用二进制精确表示。正如娄所说,不要指望浮点数的相等性。曾经。检查这些数字是否在彼此之间的某个微小范围内(例如,(a - m &lt; b) &amp;&amp; (a + m &gt; b))。
        【解决方案7】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-10-10
          • 2010-11-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-01
          • 2011-10-23
          • 1970-01-01
          相关资源
          最近更新 更多