举个简单的例子。
void foo(double x, double y)
{
if (cos(x) != cos(y)) {
std::cout << "Huh?!?/n";
}
}
int main()
{
foo(1.0, 1.0);
return 0;
}
在大多数的计算机中,程序会执行这一行:
std::cout << "Huh?!?/n";
可以在你的计算机上试一下这个例子。有些会这行代码,有些则不会,有些执行结果则依赖于编译器本身,或者选项,或者硬件,甚至月相。
为什么会出现这样的结果呢?答案是:浮点运算和比较通常由包含特殊寄存器的硬件来执行。而这些寄存器的位数通常比一个double类型还要多。这就意味着在浮点运算的过程中,中间的结果包含的位数通常大于sizeof(double)。当一个浮点数写入内存中时,通常会被删节掉一些位。换句话说,中间结果通常比写入内存的结果更加的精确。因此,当用一个存入内存的浮点数去和寄存器中的浮点数相比较时,结果也许会与人们预想的不同。
假设你的代码计算了cos(x)的值,并删节后存入一个名为tmp的临时变量中,然后计算cos(y)的值,并将结果(未删节)与tmp相比较。用虚拟的类汇编语言重写这个比较过程:
fp_load x ;将x放入一个寄存器中
call _cos ;调用
cos(double)
,并将结果存回寄存器中
fp_store tmp ;删节寄存器中的浮点数的某些位,并存入一个临时的本地变量tmp中
fp_load y ;将y放入一个寄存器中
call _cos ;调用
cos(double)
,并将结果存回寄存器中
fp_cmp tmp ;比较未删节的结果与存入tmp的已删节的结果
...
可以看到这样所得出的结果往往不是我们所预想的结果,而这结果依赖于很多细节。那么如果我们将 cos(x) 和 cos(y)全部放入本地变量中,并在执行完某些指令后再去比较他们,结果是不是就应该一样了 呢?答案也许会让你大吃一惊。看下面这段代码:
{
double cos_x = cos(x);
double cos_y = cos(y);
... ← 其他的指令,不改变cos_x和cos_y
if (cos_x != cos_y) {
std::cout << "Huh?!?/n"; ← 即使 x == y ,程序还是可能执行这一行
}
}
原因:如果编译器知道你并没有在计算cos的值和比较cos值之间使用任何浮点寄存器,那么它可能并不会将cos(y)存入变量cos_y中,而是直接将寄存器中的值与cos_x进行比较。这样的话,结果将和上面的例子相同。如果在这期间,你输出了其中的一个变量或者两个,又或者指令使用到了浮点寄存器,那么编译器将会(可能)需要存储cos(y)的结果到变量cos_y中。在这种情况下,程序将会比较两个都做了删节的值,那么结果就有可能与预想的一样。