jxh*_*jxh 25 c c++ floating-point gcc
我在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;
}
Run Code Online (Sandbox Code Playgroud)
产生以下输出:
(int)1.000000 = 1
(int)1.000000 = 0
Run Code Online (Sandbox Code Playgroud)
编译器做了什么来允许这种情况发生?
我发现这个不变,因为我正在追踪为什么下面的程序没有0按照我的预期产生(使用19 9秒产生0我预期的):
int main () {
long double x = .99999999999999999999L; /* 20 9's */
int y = x;
printf("%d\n", y);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我试图计算结果从预期切换到意外时的值时,我得出了这个问题的常数.
caf*_*caf 31
您的问题是long double您的平台上的精度不足以存储精确值0.99999999999999999999.这意味着必须将其值转换为可表示的值(此转换在程序转换期间发生,而不是在运行时).
此转换可以生成最接近的可表示值,也可以生成下一个更大或更小的可表示值.选择是实现定义的,因此您的实现应该记录它正在使用的内容.您的实现似乎使用x87样式的80位long double,并且四舍五入到最接近的值,从而导致存储的值为1.0 x.
假定格式long double(具有64个尾数位),小于1.0的最高可表示数字为十六进制:
0x0.ffffffffffffffff
Run Code Online (Sandbox Code Playgroud)
正好在此值与下一个较高可表示数字(1.0)之间的数字是:
0x0.ffffffffffffffff8
Run Code Online (Sandbox Code Playgroud)
你的长常数0.9999999999999999999728949456878623891498136799780等于:
0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...
Run Code Online (Sandbox Code Playgroud)
如果舍入到最近,显然应向下舍入,但您似乎已达到编译器使用的浮点表示的某个限制,或舍入错误.
编译器使用二进制数.大多数编译器都做同样的事情.
根据wolframalpha,二进制表示
0.99999999999999999999
看起来像这样:
0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...
Run Code Online (Sandbox Code Playgroud)
这是932位,并且STILL不足以精确地表示您的数字(见最后的点).
这意味着只要您的基础平台使用2的基数来存储数字,您将无法准确存储0.99999999999999999999.
由于数字无法精确存储,因此可以向上或向下舍入.20 9s最终被围绕,19 9s最终被向下舍入.
为了避免这个问题,你需要使用某种第三方数学/ bignum库,使用十进制基数(即每个字节或两个十进制数字)或者使用分数(比率)而不是浮点数来代替双数数字.那样可以解决你的问题.