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

时间:2022-04-09 02:26:09

I discovered on my x86 VM (32 bit) that the following program:

我在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;
}

Produces the following output:

产生以下输出:

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

Ideone also produces this behavior.

Ideone也会产生这种行为。

What is the compiler doing to allow this to happen?

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

I found this constant as I was tracking down why the following program didn't produce 0 as I expected (using 19 9s produced the 0 I expected):

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

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

As I tried to compute the value at which the result switches from expected to unexpected, I arrived at the constant this question is about.

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

4 个解决方案

#1


31  

Your problem is that long double on your platform has insufficient precision to store the exact value 0.99999999999999999999. This means that the value of that must be converted to a representable value (this conversion happens during translation of your program, not at runtime).

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

This conversion can generate either the nearest representable value, or the next greater or smaller representable value. The choice is implementation-defined, so your implementation should document which it is using. It seems that your implementation uses x87-style 80bit long double, and is rounding to the nearest value, resulting in a value of 1.0 stored in x.

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


With the assumed format for long double (with 64 mantissa bits), the highest representable number less than 1.0 is, in hexadecimal:

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

0x0.ffffffffffffffff

The number exactly halfway between this value and the next higher representable number (1.0) is:

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

0x0.ffffffffffffffff8

Your very long constant 0.9999999999999999999728949456878623891498136799780 is equal to:

你的长常数0.9999999999999999999728949456878623891498136799780等于:

0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...

which should obviously be rounded down if rounding to nearest, but you appear to have reached some limit of the floating point representation your compiler is using, or a rounding bug.

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

#2


5  

Compiler uses binary numbers. Most compilers do the same thing.

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

According to wolframalpha, binary representation of

根据wolframalpha,二进制表示

0.99999999999999999999

0.99999999999999999999

looks like this:

看起来像这样:

0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...

That's 932 bits, and that STILL isn't enough to precisely represent your number (see dots at the end).

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

Which means that as long as your underlying platform uses base of 2 to store numbers, you will not be able to store exactly 0.99999999999999999999.

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

Because number cannot be stored precisely, it'll be rounded up or down. With 20 9s it ends up being rounded up, and with 19 9s it ends up being rounded down.

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

To avoid this problem, instead of doubles you'll need to use some kind of 3rd party mathematics/bignum library that stores numbers internally using decimal base (i.e. two decimal digits per byte or something) or uses fractions (ratios) instead of floating point numbers. That would solve your problem.

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

#3


3  

Double values, when there is not enough precision to represent a value, rounds up or down to the closest one. In your implementation it is rounding up to 1.

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

#4


2  

There are two conversions involved here. First, and in some ways most important, is the conversion of the literal .99999999999999999999L to long double. As others have said, this conversion rounds to the nearest representable value, which seems to be 1.0L. The second conversion is from the long double value that resulted from the first conversion to an integer value. That conversion rounds toward 0, which is why a quick examination suggests that the value of y should be 0. But because the first conversion produced 1 and not a value slightly less than 1, this conversion also produces 1.

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

#1


31  

Your problem is that long double on your platform has insufficient precision to store the exact value 0.99999999999999999999. This means that the value of that must be converted to a representable value (this conversion happens during translation of your program, not at runtime).

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

This conversion can generate either the nearest representable value, or the next greater or smaller representable value. The choice is implementation-defined, so your implementation should document which it is using. It seems that your implementation uses x87-style 80bit long double, and is rounding to the nearest value, resulting in a value of 1.0 stored in x.

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


With the assumed format for long double (with 64 mantissa bits), the highest representable number less than 1.0 is, in hexadecimal:

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

0x0.ffffffffffffffff

The number exactly halfway between this value and the next higher representable number (1.0) is:

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

0x0.ffffffffffffffff8

Your very long constant 0.9999999999999999999728949456878623891498136799780 is equal to:

你的长常数0.9999999999999999999728949456878623891498136799780等于:

0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...

which should obviously be rounded down if rounding to nearest, but you appear to have reached some limit of the floating point representation your compiler is using, or a rounding bug.

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

#2


5  

Compiler uses binary numbers. Most compilers do the same thing.

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

According to wolframalpha, binary representation of

根据wolframalpha,二进制表示

0.99999999999999999999

0.99999999999999999999

looks like this:

看起来像这样:

0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...

That's 932 bits, and that STILL isn't enough to precisely represent your number (see dots at the end).

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

Which means that as long as your underlying platform uses base of 2 to store numbers, you will not be able to store exactly 0.99999999999999999999.

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

Because number cannot be stored precisely, it'll be rounded up or down. With 20 9s it ends up being rounded up, and with 19 9s it ends up being rounded down.

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

To avoid this problem, instead of doubles you'll need to use some kind of 3rd party mathematics/bignum library that stores numbers internally using decimal base (i.e. two decimal digits per byte or something) or uses fractions (ratios) instead of floating point numbers. That would solve your problem.

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

#3


3  

Double values, when there is not enough precision to represent a value, rounds up or down to the closest one. In your implementation it is rounding up to 1.

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

#4


2  

There are two conversions involved here. First, and in some ways most important, is the conversion of the literal .99999999999999999999L to long double. As others have said, this conversion rounds to the nearest representable value, which seems to be 1.0L. The second conversion is from the long double value that resulted from the first conversion to an integer value. That conversion rounds toward 0, which is why a quick examination suggests that the value of y should be 0. But because the first conversion produced 1 and not a value slightly less than 1, this conversion also produces 1.

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