为什么在float文字的末尾添加0会改变它的轮次(可能是GCC错误)?

我在x86 VM(32位)上发现了以下程序:

#include  void foo (long double x) { int y = x; printf("(int)%Lf = %d\n", x, y); } int main () { foo(.9999999999999999999728949456878623891498136799780L); foo(.999999999999999999972894945687862389149813679978L); return 0; } 

产生以下输出:

 (int)1.000000 = 1 (int)1.000000 = 0 

Ideone也会产生这种行为。

编译器做了什么来实现这一点?

我发现这个不变,因为我正在追踪为什么以下程序没有像我预期的那样产生0 (使用19 9 s产生了我预期的0 ):

 int main () { long double x = .99999999999999999999L; /* 20 9's */ int y = x; printf("%d\n", y); return 0; } 

当我试图计算结果从预期切换到意外时的值时,我得出了这个问题的常数。

您的问题是平台上的long double精度不足以存储精确值0.99999999999999999999。 这意味着必须将其值转换为可表示的值(此转换在程序转换期间发生,而不是在运行时)。

此转换可以生成最接近的可表示值,也可以生成下一个更大或更小的可表示值。 选择是实现定义的,因此您的实现应该记录它正在使用的内容。 您的实现似乎使用x87样式的80位long double精度,并且舍入到最接近的值,导致x存储的值为1.0。


使用long double (64位尾数位)的假设格式,小于1.0的最高可表示数字为hex:

 0x0.ffffffffffffffff 

正好在此值与下一个较高可表示数字(1.0)之间的数字是:

 0x0.ffffffffffffffff8 

你的长常数0.9999999999999999999728949456878623891498136799780等于:

 0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec... 

如果舍入到最近,显然应向下舍入,但您似乎已达到编译器使用的浮点表示的某个限制,或舍入错误。

编译器使用二进制数。 大多数编译器都做同样的事情。

根据wolframalpha,二进制表示

0.99999999999999999999

看起来像这样:

 0.... 

这是932位,并且STILL不足以精确地表示您的数字(见最后的点)。

这意味着只要您的底层平台使用2的基数来存储数字,您将无法准确存储0.99999999999999999999

由于数字无法精确存储,因此可以向上或向下舍入。 20 9s最终被围绕,并且19 9s最终被向下舍入。

为了避免这个问题,你需要使用某种第三方数学/ bignum库,使用十进制基数(即每个字节或两个小数位)存储数字,或者使用分数(比率)而不是浮点数来代替双打数字。 这可以解决你的问题。

当没有足够的精度来表示值时,双精度值向上或向下舍入到最接近的值。 在您的实现中,它最多为1。

这里涉及两次转换。 首先,在某些方面最重要的是将文字.99999999999999999999L转换为long double。 正如其他人所说的那样,这种转换是最接近的可表示值,似乎是1.0L 。 第二次转换是从第一次转换为整数值得到的long double值。 该转换向0舍入,这就是为什么快速检查表明y的值应该为0.但是因为第一次转换产生1而不是略小于1的值,所以此转换也产生1。