printf和scanf如何处理浮点数精度格式?

5

Consider the following snippet of code:

float val1 = 214.20;
double val2 = 214.20;

printf("float : %f, %4.6f, %4.2f \n", val1, val1, val1);
printf("double: %f, %4.6f, %4.2f \n", val2, val2, val2);

输出结果为:

float : 214.199997,  214.199997, 214.20 | <- the correct value I wanted 
double: 214.200000,  214.200000, 214.20 |

我知道 214.20 在二进制中是无限的。第一行的前两个元素近似于预期值,但最后一个元素似乎根本没有近似值,这引发了以下问题: scanf,fscanf,printf,fprintf(等等)函数如何处理精度格式?
如果没有提供精度,则 printf 将打印出一个近似值,但使用 %4.2f 可以获得正确的结果。你能解释一下这些函数处理精度时所使用的算法吗?
3个回答

11
事实上,214.20无法用二进制准确表示,很少有十进制数可以。因此,存储的是一个近似值。当您使用printf时,二进制表示将转换为十进制表示,但它仍然无法准确表示,只能进行近似。正如您注意到的那样,您可以给printf提供精度,告诉它如何舍入十进制近似值。如果您不提供精度,则假定精度为6(有关详细信息,请参见手册)。如果您在上面的示例中使用“%.40f”来表示浮点数和“%.40lf”来表示双精度,则会得到以下结果:
214.1999969482421875000000000000000000000000
214.1999999999999886313162278383970260620117

他们不同,因为使用双精度时,有更多的位来更好地近似于214.20。但是正如您所看到的,当用十进制表示时,它们仍然非常奇怪。
我建议阅读浮点数的维基百科文章以获取有关浮点数如何工作的更多见解。还有一篇优秀的阅读材料是每个计算机科学家都应该了解的浮点运算知识

谢谢您的回答,但我的问题仍然没有解决。首先,浮点数本身就不能包含精确值214.20,那么它如何仅使用%4.2f精度恢复该值,而不是%4.6f?它做了什么来恢复这个值。我知道进行了近似处理,但是使用了什么位模式导致内部存储的214.199997变为214.20,我们又是如何得出这个结果的呢? - ultimate cause
不确定我是否理解你的意思,但这只是四舍五入。 %4.2f 恰好给出了你想要的值纯属巧合。 它仍然是“错误”的,因为二进制表示与 214.20 不等价,而是等于 214.1999969482421875。 现在,如果将 "214.1999969482421875" 四舍五入到 6 位数字,则会得到 "214.199997",因为第七个数字是 9,将第六个数字变为 7。 但是当您仅将其四舍五入到 2 位数字时,您将得到 "214.20",因为第三位数字是 9,因此您需要将第二位数字上的 9 增加。那个“溢出”到10,将“.19”变成“.20”。 - DarkDust
哦!!我相信你想表达的是这个操作是使用十进制表示完成的,这就是双精度舍入效果形成214.199997的原因。因为如果用二进制表示,我们已经确认214.20无法被准确表示,即使使用所有的舍入技巧。 - ultimate cause
是的,那就是我想说的 :-) - DarkDust

4
自从您问到scanf,有一件事情需要注意,那就是POSIX要求printf和随后的scanf(或strtod)可以重建原始值,并确保至少打印了足够重要的数字(我相信至少是DECIMAL_DIG)。当然,普通的C语言并不要求这样做;C标准几乎允许浮点操作给出实现者想要的任何结果,只要他们记录它。但是,如果您的意图是将浮点数存储在文本文件中以便稍后读取,最好使用C99的%a指定符以十六进制打印它们。这样它们将是精确的,而且关于序列化/反序列化过程是否失去精度没有任何困惑。
请记住,当我说“重建原始值”时,我的意思是传递给printf的变量/表达式中实际保存的值,而不是源文件中写入的原始十进制值,该值由编译器舍入为最佳二进制表示形式。

1

scanf 会将输入值四舍五入为最接近的可表示浮点数值。正如DarkDust的答案所说明的那样,在单精度下,最接近的可表示值低于实际值,在双精度下,最接近的可表示值高于实际值。


如果可能的话,为什么我们要首先存储近似值呢?:-)。如果我造成了痛苦,请原谅,但这就是我想知道的,scanf如何表示将六位小数转换为两个小数点所给定的值。鉴于0.20在二进制浮点数中不可表示。 - ultimate cause
这个后续问题似乎在与DarkDust的评论对话中得到了回答。 - Philip Starhill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接