【问题标题】:Why does printf("%.3f\n", 123456.987654) print 123456.984? [closed]为什么 printf("%.3f\n", 123456.987654) 打印 123456.984? [关闭]
【发布时间】:2020-09-26 08:34:52
【问题描述】:

为什么下面的 printf 打印的是 123456.984 而不是 123456.988?

#include <stdio.h>
int main()
{
    printf("%.3f\n", 123456.987654);
    return 0;
}

编辑:我做错了,正确的 printf 是:

printf("%.3f\n", 123456.987654f);

【问题讨论】:

  • 也许123456.984123456.987654 的最接近(四舍五入)近似值?参见例如Is floating point math broken?了解详情。
  • @Someprogrammerdude: 不,123456.987654double,C 标准要求 double 具有足够的精度以产生“123456.988”。 (DBL_DIG 必须至少为 10,因此任何 10 位有效数字十进制数都可以四舍五入为 double 并再次返回而无需更改。)
  • 问题中当前显示的代码在 macOS 10.14.6 上使用 Apple Clang 11.0.0 输出“123456.988”,我希望它也可以在其他常见的 C 实现中使用。您使用的是什么编译器、编译器版本、编译器标志/开关和操作系统?为确保这是生成所述输出的代码,请删除可执行文件,尝试执行它以确保它已消失,然后从头开始重建它。
  • @EricPostpischil 在this 评论之前的问题中,OP 声明他/她在 Windows 下使用带有 MingW-gcc 的 Code::Blocks。可能这就是她/他的设置。
  • 在没有来自 OP 的进一步信息和编辑的情况下,这应该被关闭,因为不可复制。问题中显示的代码不会产生正确的 C 实现中要求的输出。 OP 可能有打印 float 对象或常量的代码,这可以解释输出,但目前提出的问题似乎不正确。

标签: c floating-point printf double precision


【解决方案1】:

错误肯定与浮点精度有关。它可以用float 常量复制。

使用浮点常量执行此 printf() 会显示您描述的行为。然而,如果你传递一个双精度常量,结果与预期的一样:

#include <stdio.h>
int main()
{
    printf("%.3f\n", 123456.987654f);  // float: insufficient precision .984
    printf("%.3f\n", 123456.987654);   // double: better precision .988
                    // unsuffixed floating point constants are double
    return 0;
}

Online demo

这是由于 floating point numbers 内部表示的近似值,它使用二的幂和二的幂的分数来表示数字。转换回十进制时与数字 might not be close enough 对应的最接近的表示形式。

这种奇怪的舍入行为取决于实现:C 标准没有指定必须使用哪种浮点表示。

您使用的编译器肯定是基于IEEE 754 标准的。在this great web page 上,您可以验证如何使用 IEEE 以单精度对所选浮点数进行编码:对于 123456.987654,几乎使用了所有位范围,并且可以以单精度表示的最接近的数字是 123456.984375。

【讨论】:

  • 关于“二的幂和二的分数”:我认为您的意思是“二的幂,包括具有负整数指数的幂。”
  • @EricPostpischil 确实没有 d,因为默认情况下它是 d。之所以使用它,是因为编译器允许它,并且它把重点放在了这个问题上。是的,对于科学家来说,二的幂包括正负的全部范围。但并不是每个人都对数学感到满意,所以我更喜欢措辞更明确:-)
  • @EricPostpischil 我已经删除了 d 并在 cmets 中进行了澄清以使其更加明确。并提供了一个很好的参考,方便分析IEEE754的单精度编码。
  • @RobertSsupportsMonicaCellio 根据 C 标准 %f 和 %F 处理双参数:“表示浮点数的双参数转换为样式 [-]ddd.ddd 的十进制表示法,其中小数点字符后的位数等于精度规范。" (N2176,第 7.21.6 节)。 - 当调用 printf() 时,无论如何都会执行双重提升。所以问题发生在双重提升之前,即在提供的浮点数中,因为它是提供的浮点值按原样转换为双精度。
【解决方案2】:

在我的电脑上打印出下面的程序(用 gcc 编译)

123456.988
123456.988
123456.984

您问题中的代码实际上是显示问题的原因,还是涉及浮点数?如果有浮点数,那么问题是浮点数只有大约 7 个数字的足够精度。打印到小数点后 3 位时,与您的数字最接近的浮点数约为 123456.984

#include    <stdio.h>
#include    <stdlib.h>

    int main( void)
    {
        printf("%.3f\n", 123456.987654);
        double  d = 123456.987654;
        printf("%.3f\n", d);
        float   f = 123456.987654;
        printf("%.3f\n", f);
        return EXIT_SUCCESS;
    }

【讨论】:

    【解决方案3】:

    您遇到的问题似乎是特定于实现/编译器的(甚至可能与编译或执行相关)。这对我来说是不可复制的。

    使用在线 Godbolt 编译器和执行器上的 gcc 9.1 版和 clang 9.0 版,两者的输出均为 123456.988

    这里是Link

    【讨论】:

    • 我认为 OP 将实际存在于浮点变量中的东西作为常量。如果你给常量添加一个 f 后缀,你可以重现它:godbolt.org/z/8nonV- 有趣的是,float clang 发出了关于双重提升的提示
    • @Christophe 是的,类似的事情是可能的,您的回答非常好,但总的来说,我们将按原样采用代码。如果 OP 真的没有显示原始发行代码,那就不好了,因为我们需要一个完整的 MRE。 - 但我不认为OP真的以这种方式搞砸了。我会继续写评论你的问题,here
    猜你喜欢
    • 2017-01-31
    • 2017-05-17
    • 1970-01-01
    • 2021-03-28
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-28
    相关资源
    最近更新 更多