【问题标题】:Explanation for floating point rounding effect浮点舍入效果说明
【发布时间】:2021-07-20 16:05:27
【问题描述】:
#include <stdio.h>
int main() {
    printf("%.14f\n", 0.0001f * 10000000.0f);  // 1
    printf("%.14f\n", 0.001f * 1000000.0f);  // 2
    printf("%.14f\n", 0.01f * 100000.0f);  // 3
    return 0;
}

Godbolt

这段代码的输出是:

1000.00000000000000
1000.00006103515625
1000.00000000000000

我知道,小数不能用浮点数精确表示。但是为什么第 1 行和第 3 行的计算正确而第 2 行却没有?您对这里的详细情况有清楚的解释吗?

【问题讨论】:

  • 埃里克很快就会给出一个更好的答案,所以我只想说:有时你会在一个地方四舍五入,在另一个地方四舍五入,然后他们取消了。此外,一般来说,您不一定会说第 1 行和第 3 行的计算正确,只是剩余的错误小于%.14f 会显示。 (但是,是的,对于传统的double%.14f 应该向您展示几乎所有内容。)
  • 扩展我的第一条评论,最接近 0.01 的浮点数是 0.0099999997,最接近 0.001 的浮点数是 0.001000000047,最接近 0.0001 的浮点数是 0.000099999997。所以有一点规律。
  • “即使是停止的时钟一天也能正确两次。” :-)
  • 设计浮点算法的人花了很多时间试图满足这个要求,因为这并不总是那么容易。通常,您必须在内部使用更多(有时更多)位来保存中间结果,以便最终结果可以正确四舍五入。最终结果应该在半个“unit in the last place”以内。
  • 例如,hypot(x,y) 被定义为只做sqrt(x*x + y*y),但在 glibc 中实现为更复杂的github.com/bminor/glibc/blob/master/sysdeps/ieee754/dbl-64/…,以免导致溢出并保持准确性

标签: c floating-point


【解决方案1】:

有时累积舍入(OP 样本中的每个 3 步)的结果与数学/十进制的结果相同,有时则不然。 @Steve Summit, @Steve Summit


对这里发生的事情的详细解释清楚吗?

每行代码都有 3 个可能的四舍五入步骤:

  • 源代码至float。回忆一下常见的float 的形式是:some_limited_integer * 2some_power

  • float 乘法与舍入

  • float 的打印四舍五入到小数点后 14 位*1


对于printf("%.14f\n", 0.0001f * 10000000.0f); // 1

  • 0.0001f 编码为float,值为0.0000999999974737875163555145263671875

  • 0.0000999999974737875163555145263671875 * 10000000.0 --> 999.999974737875163555145263671875 --> 四舍五入到最接近的float --> 1000.0

  • 1000.0 --> "1000.00000000000000".


对于printf("%.14f\n", 0.001f * 1000000.0f); // 2

  • 0.001f 编码为float,值为0.001000000047497451305389404296875

  • 0.001000000047497451305389404296875 * 1000000.0 --> 1000.000047497451305389404296875 --> 四舍五入到最接近的float --> 1000.00006103515625

  • 1000.00006103515625 --> "1000.00006103515625".


在 #1 中,舍入先向下,然后向上 - 趋于取消。
在 #2 中,四舍五入越来越高 - 导致明显的 double rounding 效果。

粗略地说,每个步骤最多可能注入 1/2 ULP 错误。


其他注意事项:1)替代舍入模式。以上使用四舍五入。 2)弱库。以上假设质量为printf()


*1 在 OP 的样本中,没有舍入误差。一般情况下,打印float"%f"可以四舍五入。

【讨论】:

    【解决方案2】:

    回答这个问题的另一种方式是说您确实没有得到一个“错误”答案和两个“正确”答案。您实际上得到了三个“正确”的答案,其中“正确”的意思是“与预期的一样好”。

    键入float 只能为您提供大约 7 个十进制数字的精度。所以对于 1000 范围内的数字,小数点后三位。所以把程序改成这样:

    printf("%.3f\n", 0.0001f * 10000000.0f);
    printf("%.3f\n", 0.001f * 1000000.0f);
    printf("%.3f\n", 0.01f * 100000.0f);
    

    输出是:

    1000.000
    1000.000
    1000.000
    

    没有差异,所有答案显然都是正确的。

    或者,用指数表示法,小数点前一位,小数点后六位。

    printf("%.6e\n", 0.0001f * 10000000.0f);
    printf("%.6e\n", 0.001f * 1000000.0f);
    printf("%.6e\n", 0.01f * 100000.0f);
    

    给予

    1.000000e+03
    1.000000e+03
    1.000000e+03
    

    同样,所有答案都一样。

    这可能看起来像是“作弊”:我们碰巧知道在我们可以看到的右侧的数字中发生了一些“有趣”的事情,所以以这种方式压制它们不是错的,而且让所有答案看起来都一样?我不打算回答这个问题,只是指出当你在做浮点运算时,几乎总是右边有一些东西,你正在四舍五入并且压制——这只是程度的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-09
      • 2020-06-09
      相关资源
      最近更新 更多