代码之家  ›  专栏  ›  技术社区  ›  Dimitris

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

  •  23
  • Dimitris  · 技术社区  · 16 年前

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

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

    其中,currentValue是float属性。

    然而,当我拥有平等和 self.scroller.currentValue = 0.1 if语句未完成,代码未执行!我发现我可以通过将0.1强制转换为float来修复这个问题。这样地:

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

    这个很好用。

    有人能向我解释为什么会这样吗?0.1是默认定义为double还是什么?

    谢谢。

    7 回复  |  直到 9 年前
        1
  •  30
  •   Georg Schölly Crazy Developer    16 年前

    我认为,在没有找到这样说的标准的情况下,当比较 float 到A double 这个 浮动 被铸造成 双重的 在比较之前。不带修饰符的浮点数被认为是 双重的 在C.

    然而,在C语言中,浮点数和双精度数没有0.1的精确表示。现在,使用float会产生一个小错误。使用double会产生更小的错误。现在的问题是,通过 浮动 到A 双重的 你的失误越大 浮动 . 当然,他们现在还没走,比起来是平等的。

    而不是使用 (float)0.1 你可以使用 0.1f 读起来更好一点。

        2
  •  6
  •   James Snook    10 年前

    问题是,正如您在问题中所建议的,您正在比较浮点数和双精度数。

    比较浮点数有一个更普遍的问题,这是因为当您对浮点数进行计算时,计算结果可能与您期望的不完全相同。产生的浮点的最后一位是错误的,这是很常见的(尽管误差可能大于最后一位)。如果你使用 == 为了比较两个浮点数,所有的位都必须相同才能使浮点数相等。如果你的计算结果稍微有点不准确,那么当你期望它们时,它们就不会相等。不必像这样比较这些值,您可以比较它们,看看它们是否几乎相等。要做到这一点,您可以取浮点数之间的正差,看看它是否小于给定值(称为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) (结果应该相同)。在这种情况下,您的比较例程必须查看您放入计算中的数字,以了解答案的误差范围。

        3
  •  4
  •   MarkPowell    16 年前

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

    The IEEE Float Point article on wikipedia 可能有助于您理解这一区别。

        4
  •  4
  •   David Thornley    16 年前

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

    因此,取一个0.1的浮点值,它会失去精度,将其扩展到double,并期望它与double 0.1进行比较,后者会降低精度。

    假设我们是用十进制来计算的,浮点数是三位数,双精度是六位数,我们将其与1/3进行比较。

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

        5
  •  4
  •   epatel    16 年前

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

    0.0001100110011001100110011001100110011001100110011...
    

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

        6
  •  1
  •   Lou Franco    16 年前

    通常,在任何语言中,您都不能真正依赖于类浮点数类型的相等性。在您的例子中,因为看起来您有更多的控制权,所以默认情况下0.1不是浮动的。您可能可以通过sizeof(0.1)(vs.sizeof(self.scroller.currentValue))找到答案。

        7
  •  -1
  •   Henry Sou    9 年前

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

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