为什么x==y而cos(x) != cos(y)(或者sin,tan,log等其他浮点运算)?

          出乎大多数人的意料,浮点运算并不像人们想像的那样工作。更糟的是,这种情况随着计算机浮点硬件或者编译器的优化选项的不同而不同。

        举个简单的例子。

#include <cmath>
 
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)全部放入本地变量中,并在执行完某些指令后再去比较他们,结果是不是就应该一样了 呢?答案也许会让你大吃一惊。看下面这段代码:

 void foo(double x, double 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中。在这种情况下,程序将会比较两个都做了删节的值,那么结果就有可能与预想的一样。

    记住:浮点数的比较微妙而且充满危险,一定要小心。浮点数常常给大多数的程序员带来“惊喜”。因此如果一定要使用浮点数,必须要首先知道浮点数到底是如何运算的。      
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值