【问题标题】:How do printf and scanf handle floating point precision formats?printf 和 scanf 如何处理浮点精度格式?
【发布时间】:2010-09-24 18:45:25
【问题描述】:

考虑下面的sn-p代码:

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 具有无限的二进制表示。第一行的前两个元素具有预期值的近似值,但最后一个似乎根本没有近似值,这导致我提出以下问题:

scanffscanfprintffprintf(等)函数如何处理精度格式?

在没有提供精度的情况下,printf 打印出一个近似值,但使用%4.2f 它给出了正确的结果。你能解释一下这些函数用来处理精度的算法吗?

【问题讨论】:

    标签: c floating-point double precision


    【解决方案1】:

    问题是,214.20 不能用二进制表示准确。很少有十进制数可以。因此存储了一个近似值。现在当你使用 printf 时,二进制表示变成了十进制表示,但又不能精确表示,只能近似表示。

    正如您所注意到的,您可以给 printf 一个精度来告诉它如何舍入小数近似值。如果你不给它一个精度,那么假定精度为 6(有关详细信息,请参阅手册页)。

    如果您在上面的示例中使用%.40f 作为浮点数,而%.40lf 作为双精度数,您将得到以下结果:

    214.1999969482421875000000000000000000000000
    214.1999999999999886313162278383970260620117
    

    它们是不同的,因为使用双精度,有更多位可以更好地接近 214.20。但正如你所见,以十进制表示时它们仍然很奇怪。

    我建议阅读Wikipedia article on floating point numbers,了解有关浮点数如何工作的更多信息。一个优秀的阅读也是What Every Computer Scientist Should Know About Floating-Point Arithmetic

    【讨论】:

    • 感谢您的回答,尽管我的问题仍然在同一个地方。首先,浮点数本身不能包含 214.20 的确切值,那么它如何才能仅以 %4.2f 精度而不是 %4.6f 重新获得它。它做了什么来恢复价值。我知道近似值已经完成,但是导致内部存储的 214.199997 变为 214.20 的位模式是什么?我们是如何到达那个位置的?
    • 不确定我是否理解您,但这只是rounding%4.2f 完全有机会为您提供您想要的价值。它仍然是“错误的”,因为二进制表示等于 214.20 而是等于 214.1999969482421875。现在,如果你将“214.1999969482421875”四舍五入到 6 位,你会得到“214.199997”,因为第 7 位是 9,将 6 变成 7。但是当你只四舍五入到 2 位时,你会得到“214.20”,因为第 3 位是 9,所以你需要增加第 2 位的 9。这“溢出”到 10,将“.19”变成“.20”。
    • 哦!我相信你想说这个操作是用十进制表示的,这就是 214.199997 是如何形成双精度的圆形效果的。因为如果它是二进制表示,我们已经确认 214.20 不能用二进制表示,使用了所有舍入技巧。
    • 是的,这就是我想说的:-)
    【解决方案2】:

    既然您询问了scanf,您应该注意的一件事是,POSIX 需要printf 和随后的scanf(或strtod)来重建原始值完全因为打印了足够有效的数字(至少DECIMAL_DIG,我相信)。 Plain C 当然没有这样的要求; C标准几乎允许浮点运算给出实现者喜欢的任何结果,只要他们记录它。但是,如果您的意图是将浮点数存储在文本文件中以供稍后读取,则最好使用 C99 %a 说明符以十六进制打印它们。这样,它们将是准确的,并且不会混淆序列化/反序列化过程是否会丢失精度。

    请记住,当我说“重建原始值”时,我指的是传递给 printf 的变量/表达式中保存的实际值,而不是您在源文件中写入的原始小数,四舍五入为编译器最好的二进制表示。

    【讨论】:

      【解决方案3】:

      scanf 会将输入值四舍五入为最接近的可精确表示的浮点值。正如 DarkDust 的回答所示,在单精度中,最接近的可精确表示的值低于精确值,而在双精度中,最接近的可精确表示的值高于精确值。

      【讨论】:

      • 如果可以这样做,为什么我们首先存储近似值? :-)。对不起,如果我造成痛苦,但我想知道的是,scanf 做了什么来表示在小数点后六位给出的值以转换为小数点后的两个点。鉴于 .20 不能用二进制浮点表示
      • 看起来这个后续问题已经在评论对话框中用 DarkDust 回答了。
      猜你喜欢
      • 2017-05-20
      • 2016-09-29
      • 1970-01-01
      • 1970-01-01
      • 2012-04-15
      • 2022-01-20
      相关资源
      最近更新 更多