简体   繁体   English

浮点舍入效果说明

[英]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神箭

The output of this code is:这段代码的输出是:

1000.00000000000000
1000.00006103515625
1000.00000000000000

I know, decimal fractions cannot be represented exactly with floats.我知道,小数不能用浮点数准确表示。 But why are lines 1 and 3 calculated correctly and 2 is not?但是为什么第 1 行和第 3 行计算正确,而第 2 行却没有? Do you have a clear explanation of what is going on here in detail?你对这里发生的事情有清楚的解释吗?

Sometimes the cumulative roundings (3 steps each in OP samples) result in the same as the mathematical/decimal one, sometimes not.有时累积舍入(在 OP 样本中每个 3 步)的结果与数学/十进制相同,有时则不同。 @Steve Summit , @Steve Summit @史蒂夫峰会@史蒂夫峰会


a clear explanation of what is going on here in detail?详细说明这里发生了什么?

There are 3 steps of potential rounding for each line of code:每行代码有 3 个潜在舍入步骤:

  • Source code to float .源代码float Recall common float are of the form: some_limited_integer * 2 some_power .回想一下常见的float的形式: some_limited_integer * 2 some_power

  • float multiplication with a rounding带舍入的float乘法

  • Printing of a float rounded to 14 decimal places *1 .打印四舍五入到小数点后 14 位*1float


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

  • Code 0.0001f to a float with a value of 0.0000999999974737875163555145263671875代码0.0001ffloat ,值为 0.00009999999747378751635555145263671875

  • 0.0000999999974737875163555145263671875 * 10000000.0 --> 999.999974737875163555145263671875 --> rounded to nearest float --> 1000.0 0.0000999999747378751635555145263671875 * 10000000.0 --> 999.999974737875163555145263671875 --> 舍入到最接近的 10 float -->

  • 1000.0 --> "1000.00000000000000" . 1000.0 --> "1000.00000000000000"


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

  • Code 0.001f to a float with a value of 0.001000000047497451305389404296875代码0.001ffloat ,值为 0.001000000047497451305389404296875

  • 0.001000000047497451305389404296875 * 1000000.0 --> 1000.000047497451305389404296875 --> rounded to nearest float --> 1000.00006103515625 0.001000000047497451305389404296875 * 1000000.0 --> 1000.000047497451305389404296875 --> 四舍五入到最接近的float --> 100562.

  • 1000.00006103515625 --> "1000.00006103515625" . 1000.00006103515625 --> "1000.00006103515625"


In #1 the rounding was down, then up - tending to cancel.在#1 中,四舍五入是向下的,然后向上 - 趋于取消。
In #2, the rounding was up and up - resulting in noticeable double rounding effect.在 #2 中,四舍五入是向上和向上的 - 导致明显的双四舍五入效果。

Roughly, each step may inject up to a 1/2 ULP error.粗略地说,每一步都可能注入高达 1/2 ULP 的错误。


Other considerations: 1) alternative rounding mode.其他注意事项: 1) 替代舍入模式。 Above uses round to nearest.以上使用舍入到最近。 2) Weak libraries. 2)弱库。 Above assumes a quality printf() .以上假设质量printf()


*1 In OP's samples, there was no rounding error. *1在 OP 的样本中,没有舍入误差。 In general, printing float with "%f" can round.一般来说,用"%f"打印float可以舍入。

The other way of answering this is to say that you did not get one "wrong" answer and two "right" answers.回答这个问题的另一种方法是说,你没有得到一个“错误”的答案,两个“正确”答案。 You actually got three "right" answers, where "right" means, "as good as could have been expected".你实际上得到了三个“正确”的答案,其中“正确”的意思是“和预期的一样好”。

Type float only gives you about 7 decimal digits of precision.类型float只给你大约 7 个十进制数字的精度。 So for numbers in the range of 1000, that's three places past the decimal.因此,对于 1000 范围内的数字,这是小数点后三位。 So change the program like this:所以把程序改成这样:

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

The output is:输出是:

1000.000
1000.000
1000.000

No discrepancy, all answers apparently correct.没有差异,所有答案显然都是正确的。

Or, do it in exponential notation, with one digit before the decimal and 6 after.或者,用指数表示法进行计算,小数点前一位,后 6 位。

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

gives

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

Again, all answers the same.同样,所有答案都相同。

This might seem like "cheating": we happen to know there's some "interesting" things going on in the digits off to the right of what we can see, so isn't it wrong to suppress them in this way, and make it look like all the answers were the same?这可能看起来像是“作弊”:我们碰巧知道在我们可以看到的右边的数字中有一些“有趣”的事情,所以以这种方式压制它们并让它看起来是不是错了好像所有的答案都一样? I'm not going to answer that question, other than to point out that when you're doing floating-point work, there's almost always something off there to the right, that you're rounding off and suppressing -- it's just a matter of degree.我不打算回答这个问题,除了指出当你在做浮点运算时,几乎总是有一些东西在右边,你在四舍五入和抑制——这只是一个问题度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM