天天看点

浮点数计算精度控制

今天调试一个vc程序遇到了一个非常奇怪的问题,如下一段程序 //----------------------------------------- double x=-29568;

double y=0.0001;

if(x+y>x)

      MessageBox(0,"bigger","bigger",0);

else

{

       MessageBox(0,"smaller","smaller",0);

       exit(1);

} //------------------------------------------ 在我调试的那个工程中,x+y>x竟然为假,我切换到汇编窗口发现是下面的代码 //---------------------------- ;if(x+y>x)

0043F218; fld; qword ptr [x]

0043F21E; fadd; qword ptr [y]

0043F224; fcomp;;;;;; qword ptr [x]

0043F22A; fnstsw; ax;;

0043F22C; test;;;;;;;;;; ah,41h

0043F22F; jne;;;;;;;;;;;; ...... //---------------------------- 代码并没有错呀?问题出在哪儿呢?应该是fadd qword ptr [y]语句的执行精度的问题,因为执行这条语句后,st(0)的值竟然没变(即x+y=x). 【注】下面链接上有汇编语言浮点指令的说明,多亏它我才看懂以上几句代码. (看雪学院 www.pediy.com/tutorial/chap2/Chap2-4.htm)

进一步调试我发现,浮点数计算错误发生在一个函数(InitEngine)调用后,在此函数调用前就不会出这个问题。这个函数是一个引擎的初时化函数,无法知道他的具体实现,是什么原因造成了浮点计算错误呢?

编译选项设置不当?应该不会吧,因为生成的汇编代码是正确的呀。在汇编代码正确的情况下,浮点计算精度太小,唯一的原因只能是fpu的状态错误了。我从网上搜到了intel的开发手册,找到介绍fpu的章节发现fpu的计算精度和它的控制寄存器中的两个bit位有关。重新调试程序并注意观察fpu的控制寄存器的变化,果然在调用函数前后fpu控制字由0x027f变为了0x007f,即fpu计算精度由双精度变为了单精度,明白了这点就不难明白上面的问题了,也就有了解决的办法。

解决办法:在InitEngine调用后执行下面两段代码之一:

方法一:

WORD fctrl=0x027f;

__asm

{

   fldcw fctrl     //fpu控制寄存器设为函数调用以前的值

}

方法二:

__asm

{

   FINIT     //重置fpu状态,注意此时fpu的精度将变为扩展精度而不是双精度

}

【后记】通过这个事情我认识到了底层技术知识对于一个程序员的重要性,无论你处于计算机应用的哪个层次,如果你不了解底层技术,你就难以应付工作中偶尔出行的棘手问题(虽然仅是偶尔出现)。  

继续阅读