26

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

#include <stdio.h>
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 9s 产生了0我的预期):

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

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

4

4 回答 4

31

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

这种转换可以生成最接近的可表示值,或者下一个更大或更小的可表示值。选择是实现定义的,因此您的实现应该记录它正在使用的。您的实现似乎使用 x87 风格的 80bit long double,并且四舍五入到最接近的值,导致 1.0 的值存储在x.


对于long double(64 尾数位)的假定格式,小于 1.0 的最高可表示数字是十六进制:

0x0.ffffffffffffffff

这个值和下一个更高的可表示数字 (1.0) 之间的正好中间的数字是:

0x0.ffffffffffffffff8

你很长的常数 0.9999999999999999999728949456878623891498136799780 等于:

0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...

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

于 2013-05-30T09:41:37.763 回答
5

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

根据 wolframalpha,二进制表示

0.99999999999999999999

看起来像这样:

0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...

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

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

因为数字不能精确存储,所以会向上或向下舍入。对于 20 个 9,它最终被四舍五入,对于 19 个 9,它最终被向下舍入。

为避免此问题,您需要使用某种 3rd 方数学/bignum 库而不是双精度数,该库使用十进制基数(即每个字节或其他东西)内部存储数字或使用分数(比率)而不是浮点数数字。那会解决你的问题。

于 2013-05-30T18:40:50.393 回答
3

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

于 2013-05-30T12:21:30.173 回答
2

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

于 2013-05-30T17:56:04.000 回答