%g printf 说明符到底是什么意思?

What precisely does the %g printf specifier mean?

%g 说明符的行为方式似乎与大多数来源记录的行为方式不同。

根据我发现的大多数来源,在使用 printf 说明符的多种语言中,%g 说明符应该等同于 %f%e - 以提供的值产生更短的输出为准。例如,在撰写此问题时,cplusplus.com says g 说明符表示:

Use the shortest representation: %e or %f

PHP manual says 表示:

g - shorter of %e and %f.

here's a Stack Overflow answer声称

%g uses the shortest representation.

并且 a Quora answer 声称:

%g prints the number in the shortest of these two representations

但这种行为并不是我在现实中看到的。如果我编译并 运行 这个程序(作为 C 或 C++ - 它是一个在两者中具有相同行为的有效程序):

#include <stdio.h>

int main(void) {
    double x = 123456.0;
    printf("%e\n", x);
    printf("%f\n", x);
    printf("%g\n", x);
    printf("\n");

    double y = 1234567.0;
    printf("%e\n", y);
    printf("%f\n", y);
    printf("%g\n", y);
    return 0;
}

...然后我看到这个输出:

1.234560e+05
123456.000000
123456

1.234567e+06
1234567.000000
1.23457e+06

显然,%g 输出与 不完全匹配 x%e%f 输出或上面的 y。而且,看起来 %g 也没有最小化输出长度;如果 yx 一样 而不是 以科学记数法打印,那么 y 的格式可能会更简洁。

我上面引用的所有来源都是骗我的吗?

我在其他支持这些格式说明符的语言中看到相同或相似的行为,可能是因为在幕后它们调用了 printf 系列的 C 函数。例如,我在 Python:

中看到了这个输出
>>> print('%g' % 123456.0)
123456
>>> print('%g' % 1234567.0)
1.23457e+06

在PHP中:

php > printf('%g', 123456.0);
123456
php > printf('%g', 1234567.0);
1.23457e+6

在Ruby中:

irb(main):024:0* printf("%g\n", 123456.0)
123456
=> nil
irb(main):025:0> printf("%g\n", 1234567.0)
1.23457e+06
=> nil

控制此输出的逻辑是什么?

这是 C11 标准中 g/G 说明符的完整描述:

A double argument representing a floating-point number is converted in style f or e (or in style F or E in the case of a G conversion specifier), depending on the value converted and the precision. Let P equal the precision if nonzero, 6 if the precision is omitted, or 1 if the precision is zero. Then, if a conversion with style E would have an exponent of X:

     if P > X ≥ −4, the conversion is with style f (or F) and precision P − (X + 1).
     otherwise, the conversion is with style e (or E) and precision P − 1.

Finally, unless the # flag is used, any trailing zeros are removed from the fractional portion of the result and the decimal-point character is removed if there is no fractional portion remaining.

A double argument representing an infinity or NaN is converted in the style of an f or F conversion specifier.

这种行为有点类似于简单地使用 %f%e 中的最短表示,但并不等同。有两个重要区别:

  • 使用 %g 时会去除尾随零(可能还有小数点),这会导致 %g 说明符的输出与 不完全匹配 %f%e 会生成。
  • 关于是使用 %f 样式还是 %e 样式格式化的决定完全基于 %e 样式表示法中所需的指数大小,并且 而不是 直接取决于哪种表示形式更短。在几种情况下,此规则会导致 %g 选择更长的表示形式,例如 %g 使用科学计数法的问题中所示的情况,即使这会使输出比需要的长 4 个字符.

如果 C 标准的措辞难以解析,Python documentation 提供了相同行为的另一种描述:

General format. For a given precision <code>p >= 1, this rounds the number to <code>p significant digits and then formats the result in either fixed-point format or in scientific notation, depending on its magnitude.

The precise rules are as follows: suppose that the result formatted with presentation type <code>'e' and precision <code>p-1 would have exponent <code>exp. Then if <code>-4 <= exp < p, the number is formatted with presentation type <code>'f' and precision <code>p-1-exp. Otherwise, the number is formatted with presentation type <code>'e' and precision <code>p-1. In both cases insignificant trailing zeros are removed from the significand, and the decimal point is also removed if there are no remaining digits following it.

Positive and negative infinity, positive and negative zero, and nans, are formatted as <code>inf, <code>-inf, <code>0, <code>-0 and <code>nan respectively, regardless of the precision.

A precision of <code>0 is treated as equivalent to a precision of <code>1. The default precision is <code>6.

互联网上许多声称 %g 只是从 %e%f 中选择最短的消息来源是完全错误的。

我最喜欢的双打格式是“%.15g”。它似乎在每种情况下都做正确的事情。我很确定 15 也是双精度的最大可靠小数精度。