VC6.0下debug和release版本的浮点运算结果不一致实例分析
一帖子报告以下程序在VC6.0下debug和release版本的运算结果不同:
#define PI 3.1415926
int main(void)
{
float num1;
float num2;
float val;
int i;
val = 0;
for(i = 1; i < 100; i ++)
{
num1 = 6548.5f*i/PI;
num2 = 22234.44/i*PI;
val += num2*num1;
}
printf("%x/n", *((unsigned long*)&val));
return 0;
}
上面代码在debug下输出5056cb73,在release下输出5056cb6a。
中间关于计算val的代码可简化为:
for(i = 1; i < 100; i ++)
{
val += 145602230.34f;
}
简化后仍然是在debug下输出5056cb73,在release下输出5056cb6a。
我们知道debug版本是不优化的,而release版本采用速度优化。
debug版本中的汇编程序是:
:00410A08 C745F400000000 mov [ebp-0C], 00000000
:00410A0F C745F001000000 mov [ebp-10], 00000001
:00410A16 EB09 jmp 00410A21
:00410A18 8B45F0 mov eax, dword ptr [ebp-10]
:00410A1B 83C001 add eax, 00000001
:00410A1E 8945F0 mov dword ptr [ebp-10], eax
:00410A21 837DF064 cmp dword ptr [ebp-10], 00000064
:00410A25 7D0E jge 00410A35
:00410A27 D945F4 fld dword ptr [ebp-0C]
:00410A2A D80524604200 fadd dword ptr [00426024]
:00410A30 D95DF4 fstp dword ptr [ebp-0C]
:00410A33 EBE3 jmp 00410A18
:00410A35 …(以下略)
上述代码中把val的值存放在[ebp-0C]中。每做一次加法时,先把val从[ebp-0C] 压入协处理器的堆栈中,然后加上常数145602230.34。做完加法后把协处理器堆栈栈顶的数据传送到[ebp-0C]中。
release版本的汇编程序是:
:00401001 D905B4804000 fld dword ptr [004080B4]
:00401007 B863000000 mov eax, 00000063
:0040100C D805B0804000 fadd dword ptr [004080B0]
:00401012 48 dec eax
:00401013 75F7 jne 0040100C
:00401015 D9542400 fst dword ptr [esp]
上述代码首先把常数0压入协处理器的堆栈中,然后做99次加法,最后再把结果传送到[esp]中。
由此可见,两种版本的差异是因fld和fstp产生的。debug版本中频繁的fld和fstp操作损失了数据精度。这样看来,release版本的精度要比debug版本的高。
以下是关于fld、fst和fstp指令的资料:
1、FLD
指令格式:FLD STReg(*)/MemReal
指令功能:将浮点数据压入协处理器的堆栈中。当进行内存单元内容压栈时,系统会自动决定传送数据的精度。比如:用DD或REAL4定义的内存单元数值是单精度数等。
2、FST
指令格式:FST STReg/MemReal
指令功能:将协处理器堆栈栈顶的数据传送到目标操作数中。在进行数据传送时,系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成相应精度的数据。
3、FSTP
指令格式:FSTP STReg/MemReal
指令功能:与FST相类似,所不同的是:指令FST执行完后,不进行堆栈的弹出操作,即:堆栈不发生变化,而指令FSTP执行完后,则需要进行堆栈的弹出操作,堆栈将发生变化。
上述资料来源于:
http://topic.csdn.net/u/20071026/18/69b89fec-2dd4-40ce-a0cc-545b7721daca.html