打印非常大的浮点数

13
#include <stdio.h>
#include <float.h>

int main()
{
    printf("%f\n", FLT_MAX);
}

GNU输出的结果:

340282346638528859811704183484516925440.000000

来自Visual Studio的输出:

340282346638528860000000000000000000000.000000

C和C++标准允许两种结果吗?还是它们强制执行特定的结果?

请注意,FLT_MAX = 2^128-2^104 = 340282346638528859811704183484516925440


3
C和C++标准甚至没有规定特定的浮点数表示方式。因此,我认为它们无法强制规定特定的结果。 - Mysticial
@Mysticial 嗯,他们可能仍然普遍要求“必须打印出所表示的确切值”或类似的内容。 - fredoverflow
3
Unix 轻松战胜 Windows,再次证明自己的实力。 - user529758
@H2CO3,你怎么得出GNU比Windows更好的结论?我更喜欢微软的结果,因为它正确地暗示了分辨率是有限的。 - Mark Ransom
1
请查看我的两篇文章:http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/ 和 http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/。你会看到数字的位数如何因语言和实现而异。 - Rick Regan
请注意,Windows 打印的是可以重构相同双精度浮点数的最短十进制表示形式,而 gnu libc 则打印内部表示 mantissa*2^exponent 的精确十进制表示形式。在我看来,两者都为单精度浮点数打印了太多位数,但在 C 中如何不将 float 提升为 double 呢?对于 REPL 语言,最短表示更有价值,您需要打印您键入的内容。对于通用语言,一个好的库应该提供这两种可能性。 - aka.nice
3个回答

6
我认为C99标准中相关的部分是7.19.6.1页第13段的“推荐做法”:
对于e、E、f、F、g和G转换,如果有效十进制数字的数量最多为DECIMAL_DIG,则结果应正确四舍五入。如果有效十进制数字的数量超过DECIMAL_DIG但源值恰好可用DECIMAL_DIG位表示,则结果应为带有尾随零的精确表示。否则,源值由两个相邻的十进制字符串L < U限定,这两个字符串都有DECIMAL_DIG个有效数字;所得十进制字符串D的值应满足L <= D <= U,并且额外规定错误应具有当前舍入方向的正确符号。
我的印象是这种情况下允许打印一些余地,因此我的结论是VS和GCC在这里都是符合标准的。

5
@ColeJohnson:没有比它更精确的了。GNU会欺骗你认为它更精确,但实际上它的结果甚至比MSVC还要糟糕。浮点数表示的是“区间”,而非“数字”。因此,对于64位IEEE格式而言,340282346638528859811704183484516925440、340282346638528859811704183484516925450和340282346638528860000000000000000000000都是相同的浮点数值。一个更好的浮点数格式化程序实际上是给出最短可能的表示方式 - Yakov Galka
8
浮点数并不代表区间。它表示二进制有理数,这些数可能是精确的,也可能是舍入结果,这取决于您的使用情况。使用浮点数进行区间算术实际上非常困难(要么迅速扩大区间,要么需要不断改变舍入方向,并且需要一种尊重舍入模式的实现)。 - R.. GitHub STOP HELPING ICE
2
@FredOverflow:GNU 的结果是正确的值,但两者似乎都符合标准中的要求和推荐实践。 - R.. GitHub STOP HELPING ICE
5
这些数字并不是微不足道的。问题在于值“1e-20”已经被四舍五入了。GNU libc(不是GCC;GCC是编译器,与printf行为无关)版本会打印出浮点表达式的精确值。如果你想表示数值1e-20,最后几位可能对来说毫无意义,但printf无法知道你想要它作为1e-20的近似值还是浮点表达式的精确值。MSVCRT的行为没有任何好处;它只是懒惰的表现。 - R.. GitHub STOP HELPING ICE
6
根据IEEE浮点数算术标准754-2008规定,浮点数据仅表示特定数字(和NaN),而不是区间。根据第3.3节,所表示的数据包括+0;-0;形如(-1)s * be * m的数字,其中s为0或1,b、e和m是适用于特定浮点类型的合适基数、指数和尾数;+无穷大、-无穷大、quiet NaN和signaling NaN。浮点运算的结果由操作数所表示的个别数字决定,而不是它们周围的区间。 - Eric Postpischil
显示剩余7条评论

1

C标准允许两种方式(C++只是导入了C标准)

来自第5.2.4.2.2节的草案版本第10部分

以下列表中给出的值应该被替换为常量表达式,其实现定义的值大于或等于所示值:
— 最大可表示有限浮点数,(1 − b −p)b emax

FLT_MAX 1E+37

Visual C++ 2012拥有

#define FLT_MAX         3.402823466e+38F        /* max value */

实际上,在使用IEEE-754浮点数的任何平台上,FLT_MAX的值都将是相同的。因此这不是问题所在。 - Oliver Charlesworth
@OliCharlesworth:好吧,也许“有缺陷”这个词用得太过强烈了。它会引发未定义的结果。 - wallyk

0

代码本身存在缺陷,其中使用%f来表示大于floatdouble中所持有的显著性的值。这样做会要求您查看转换为十进制时生成的无意义保护位或其他浮点噪声的“幕后情况”。

显然,您不应该期望在本田和丰田制造的发动机之后产生任何一致的金属填料。更不用说对这种一致性的任何合理期望了。

正确显示此类数字的方法是使用其中一个“科学”格式,例如提供了未过度指定精度的%g。在IEEE-754实现中,float具有7个有效数字,double具有15-16个有效数字,long double约为19个有效数字,而__float128则为34个有效数字。因此,对于您给出的示例,%.15g是适当的,假设它在IEEE-754实现上。


1
使用“%f”并不是“有缺陷”的。问题在于它正在询问标准在“幕后”允许什么。 - Oliver Charlesworth
@OliCharlesworth:好吧,也许“有缺陷”这个词用得太强了。它会引发未定义的结果。 - wallyk

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