浮点数舍入效果说明

Explanation for floating point rounding effect

#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 行不是?您对这里发生的事情有清楚的解释吗?

有时累积舍入(OP 样本中的每个舍入 3 步)的结果与 mathematical/decimal 相同,有时则不同。 ,


a clear explanation of what is going on here in detail?

每行代码有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.00006103[532625]

  • 1000.00006103515625 --> "1000.00006103515625".


在 #1 中,四舍五入先是向下,然后向上 - 趋于取消。
在 #2 中,四舍五入越来越大 - 产生了明显的 double rounding 效果。

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


其他注意事项:1)替代舍入模式。以上使用四舍五入到最近。 2) 图书馆薄弱。以上假定质量 printf().


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

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

Type 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

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

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

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

同样,所有答案都相同。

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