【问题标题】:the precision of printf with specifier "%g"带有说明符“%g”的 printf 的精度
【发布时间】:2015-06-05 05:26:20
【问题描述】:

谁能解释一下 printf 中的[.precision] 如何与说明符“%g”一起工作?我对以下输出感到很困惑:

double value = 3122.55;
printf("%.16g\n", value); //output: 3122.55
printf("%.17g\n", value); //output: 3122.5500000000002

我了解到%g 使用最短的表示。

但以下输出仍然让我感到困惑

printf("%.16e\n", value); //output: 3.1225500000000002e+03
printf("%.16f\n", value); //output: 3122.5500000000001819
printf("%.17e\n", value); //output: 3.12255000000000018e+03
printf("%.17f\n", value); //output: 3122.55000000000018190

我的问题是:为什么%.16g 给出确切的数字而%.17g 不能?

似乎 16 位有效数字是准确的。谁能告诉我原因?

【问题讨论】:

  • 在 IEEE 浮点数中永远不会这样。
  • @user35443 实际上是 IEEE 浮点表示 (ieee-754) 导致了这种情况。

标签: c printf


【解决方案1】:

%g 使用最短的表示。

浮点数 usually aren't stored 作为基数 10 中的数字,但 2(性能、大小、实用性原因)。但是,无论您的表示的基础是什么,总会有一些有理数在变量存储它们的任意大小限制中无法表示。

当您指定%.16g 时,您的意思是您希望以最短的形式表示给定的数字,最大有效数字为16

如果最短的表示有多个16 数字,printf 将通过在最后切割2 数字来缩短数字字符串,留下3122.550000000000,实际上是3122.55最短的形式,解释你得到的结果。

一般来说,%g 将始终为您提供最短的结果,这意味着如果可以缩短代表您的数字的数字序列而不会损失任何精度,那么它就会完成。

进一步举例,当您使用%.17g 并且17th 小数位包含与0 不同的值(特别是2)时,您最终会得到完整的数字3122.5500000000002

我的问题是:为什么%.16g 给出了确切的数字,而%.17g 不能吗?

实际上是%.17g 为您提供了准确的结果,而%.16g 只为您提供了一个带有错误的四舍五入的近似值(与内存中的值相比)。

如果您想要更固定的精度,请改用%f%F

【讨论】:

  • 我知道在呈现浮点数时存在路由错误。让我感到困惑的是,前 16 位有效数字似乎实际上是我想要的,没有路由错误。为什么?
  • @cssmlulu 错误在17th。 %.16g 表示您只关心第一个 16 数字,而忽略最后一个数字。当末尾有很多零(非有效数字)时,%g 会将它们截断,使其与 3122.55 一样短。
  • @cssmlulu 除了@user35443 的注释,%g 使用有效位数作为其精度说明符。这里有更多解释:What is the difference between %g and %f in C?。有效数字规则之一指出ALL zeroes between non-zero numbers are ALWAYS significant.,如果您在3122.5500000000002 上使用%.17g,则该规则将适用,因为52 之间的零被认为是重要的。这就是包含这些零的原因。
  • 它并没有真正破坏这里论证的主旨,但请注意您声称 "%g 使用最短表示。" 是错误的。请参阅stackoverflow.com/q/54162152/1709587,我在此反驳了这一说法。
  • 不,%.17g 没有给出确切的表示。事实上,没有办法将3122.55 精确地存储在浮点数中。实际情况是,该数字存储为 (printf '%a\n' 3122.55) 0x1.865199999999ap+11,在数学上完全等于 3122.550000000000181898940354585647583007812500。但是,谈论近似值的“数学上精确”(由 ieee754 四舍五入)是没有意义的。
【解决方案2】:

十进制值 3122.55 不能用二进制浮点数精确表示。当你写

double value = 3122.55;

您最终会得到可以精确表示的最接近的可能值。碰巧,该值完全正确 3122.5500000000001818989403545856475830078125

16 位有效数字的值是3122.550000000000。对于 17 位有效数字,它是 3122.5500000000002。这些就是%.16g%.17g 给你的表示。

请注意,十进制数的最接近的double 表示保证至少精确到 15 个十进制有效数字。这就是为什么在这种情况下您需要打印到 16 位或 17 位才能开始在输出中看到这些明显的不准确性 - 对于任何较小数量的有效数字,double 表示保证与您输入的原始十进制数匹配。

最后一点:你这么说

我了解到%g 使用最短的表示。

虽然这是对%g 行为方式的流行总结,但它也是错误的。请参阅 What precisely does the %g printf specifier mean? 我详细讨论的地方,并展示使用科学记数法的 %g 的示例,即使它比不使用科学记数法要长 4 个字符。

【讨论】:

    【解决方案3】:

    十进制表示 3122.55 不能完全用二进制浮点表示。

    一个双精度二进制浮点值可以正确表示一个十进制值的大约 15 个有效数字(注意不是小数位);此后数字可能不相同,在极端情况下甚至没有任何实际意义,并且将是从浮点表示形式转换为十进制数字字符串的人工制品。

    我了解到 %g 使用最短的表示。

    规则是:

    其中 P 是精度(如果未指定精度,则为 6,如果精度为零,则为 1),X 是 E/e 样式表示法所需的十进制指数那么:

    • 如果 P > X ≥ −4,则转换为 f 或 F 样式和精度 P - 1 - X.
    • 否则,转换使用样式 e 或 E 和精度 P - 1

    precision%g 的修改导致不同的输出:

    printf("%.16g\n", value); //output: 3122.55
    printf("%.16e\n", value); //output: 3.1225500000000002e+03
    printf("%.16f\n", value); //output: 3122.5500000000001819
    

    尽管在格式说明符中具有相同的精度。

    【讨论】:

      【解决方案4】:

      十进制 3122.55 在内存中的精确表示是带有 53 位尾数的二进制小数。

      printf("%a\n", value);     // output 0x1.865199999999ap+11
      

      精确转换回小数是:

      printf("%.45f\n", value);  // output 3122.550000000000181898940354585647583007812500000
      

      如果你把这个数字减少到 17 位,你会得到:

      printf("%.17g\n", value);  // output 3122.5500000000002
      

      在 16 位时,所有尾随数字都是 0 并且可以安全地删除(g 格式默认会自动删除)以获得:

      printf("%.16g\n", value);  // output 3122.55
      

      这就是为什么你要取回原来的十进制数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-05-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-07
        相关资源
        最近更新 更多