【问题标题】:C++ How to avoid floating-point arithmetic error [duplicate]C ++如何避免浮点算术错误[重复]
【发布时间】:2013-01-29 08:56:10
【问题描述】:

我正在编写一个以浮点数递增的循环,但我遇到了以下示例所示的浮点算术问题:

for(float value = -2.0; value <= 2.0; value += 0.2)
    std::cout << value << std::endl;

这是输出:

-2
-1.8
-1.6
-1.4
-1.2
-1
-0.8
-0.6
-0.4
-0.2
1.46031e-07
0.2
0.4
0.6
0.8
1
1.2
1.4
1.6
1.8

为什么我得到的是1.46031e-07 而不是0?我知道这与浮点错误有关,但我无法理解为什么会发生这种情况以及我应该做些什么来防止这种情况发生(如果有办法的话)。有人可以解释(或指向我的链接)以帮助我理解吗?任何输入表示赞赏。谢谢!

【问题讨论】:

  • “如何避免浮点算术错误”——你不能,抱歉。
  • 这个问题已经被问过很多次了
  • 0.2 不能用 float 精确表示(假设 IEEE754 浮点运算)。如果你提高输出的精度,你可以看到这一点:example

标签: c++ floating-point floating-point-precision


【解决方案1】:

正如其他人所说,这是因为实数是无限且不可数的集合,而浮点表示使用有限数量的位。浮点数只能近似实数,即使在许多简单的情况下也不精确,因为它们的定义。正如您现在所看到的,0.2 实际上并不是0.2,而是一个非常接近它的数字。当您将这些添加到 value 时,您会在每个步骤中累积错误。

作为替代方案,请尝试使用ints 进行迭代并将结果划分以将其返回到您需要的域中:

for (int value = -20; value <= 20; value += 2) {
  std::cout << (value / 10.f) << std::endl;
}

对我来说,这给出了:

-2
-1.8
-1.6
-1.4
-1.2
-1
-0.8
-0.6
-0.4
-0.2
0
0.2
0.4
0.6
0.8
1
1.2
1.4
1.6
1.8
2

【讨论】:

  • 我不敢相信这不是公认的答案,因为只有这个和其他 1 个答案可以提供解决方案。
  • 它让我想起了与 [ULPs] 相关的著名问题 (randomascii.wordpress.com/2012/02/25/…)
【解决方案2】:

没有明确的解决方案来避免浮点精度损失。我建议您阅读以下论文:What every computer scientist should know about floating point arithmetic

【讨论】:

【解决方案3】:

这是因为浮点数只有一定的离散精度。

0.2 并不是真正的 0.2,而是在内部表示为一个稍微不同的数字。

这就是你看到不同的原因。

这在所有浮点计算中都很常见,你真的无法避免。

【讨论】:

  • 重要的是要指出,虽然 0.2 不能精确地表示为浮点数,但 -2.0 和 2.0 可以。我指出这一点只是为了避免浮点数学是任意的和反复无常的印象。所发生的一切是 float 和 double 使用基数 2,而 0.2 等价于 1/5,不能表示为有限的基数 2 数。 -2、2.0、0.5、0.25、-.375 和 178432 都可以精确表示。
【解决方案4】:

让我们做你的循环,但要提高输出精度。

代码:

for(float value = -2.0; value <= 2.0; value += 0.2)
    std::cout << std::setprecision(100) << value << std::endl;

输出:

-2
-1.7999999523162841796875
-1.599999904632568359375
-1.3999998569488525390625
-1.19999980926513671875
-0.999999821186065673828125
-0.79999983310699462890625
-0.599999845027923583984375
-0.3999998569488525390625
-0.19999985396862030029296875
1.460313825418779742904007434844970703125e-07
0.20000015199184417724609375
0.400000154972076416015625
0.6000001430511474609375
0.800000131130218505859375
1.00000011920928955078125
1.20000016689300537109375
1.40000021457672119140625
1.60000026226043701171875
1.80000030994415283203125

【讨论】:

    【解决方案5】:

    使用整数并除以:

    for(int value = -20; value <= 20; value += 2)
        std::cout << (value/10.0) << std::endl;
    

    【讨论】:

    • +1,但是...通过将value 除以10.0,您建议编译器它应该以双精度计算,然后转换为单精度(您正在尝试的 OP 程序to emulate 有一个单精度变量)。碰巧这给出了与直接单精度除法相同的结果。但是,由于它给出相同结果的原因并不简单,编译器几乎肯定会生成双精度除法代码,然后从双精度转换为单精度。因此,value / 10.0f 会稍微好一点。
    • 我刚刚检查过,GCC 确实为float r = f / 10.0; 生成了一个单精度除法。我印象深刻(我之前的评论失去了很多价值)。
    【解决方案6】:

    通过一些算法书或使用互联网了解浮点表示。那里有很多资源。

    就目前而言,您想要的似乎是某种在非常接近于零的情况下获得零的方法。我们都知道我们称这个过程为“四舍五入”。 :) 那么为什么在打印这些数字时不使用它。 printf 函数为这类事情提供了良好的格式化能力。如果您不知道如何使用 printf 进行格式化,请查看以下链接中的表格。 (您可以使用格式进行四舍五入和正确显示数字) printf 参考:http://www.cplusplus.com/reference/cstdio/printf/?kw=printf

    -- 编辑--

    也许你们中的一些人知道根据数学 1.99999999.... 与 2.0 相同。唯一的区别是表示。但是数量是一样的。

    您的浮点问题与此类似。 (这只是为了您的澄清。您的问题与 1.9999.... 的事情不一样。)

    【讨论】:

      猜你喜欢
      • 2012-11-26
      • 1970-01-01
      • 1970-01-01
      • 2019-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多