Zhe*_*hen 10 c++ floating-point assembly
最近,我编写了一个小程序,并使用2个不同版本的mingw32(在Windows8上)编译它.令人惊讶的是,我得到了两个不同的结果.我试图解除它,但没有发现什么特别的.谁能帮助我?谢谢.
exe文件:https: //www.dropbox.com/s/69sq1ttjgwv1qm3/asm.7z
结果:720720(gcc版本4.5.2),720719(gcc版本4.7.0)
编译器标志:-lstdc ++ -static
代码剪断如下:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int a = 55440, b = 13;
a *= pow(b, 1);
cout << a << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
装配输出(4.5.2):
装配输出(4.7.0):
我已经能够用单个版本的编译器重现问题.
我的是MinGW g ++ 4.6.2.
当我编译程序时g++ -g -O2 bugflt.cpp -o bugflt.exe,我得到了720720.
这是拆卸main():
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
call ___main
movl $720720, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl %eax, (%esp)
call __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
leave
ret
Run Code Online (Sandbox Code Playgroud)
如您所见,该值是在编译时计算的.
当我编译它时g++ -g -O2 -fno-inline bugflt.cpp -o bugflt.exe,我得到了720719.
这是拆卸main():
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
...
LC1:
.long 1196986368 // 55440.0 exactly
Run Code Online (Sandbox Code Playgroud)
如果我用这样的exp()加载13.0 替换调用:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
// call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fildl (%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
Run Code Online (Sandbox Code Playgroud)
我得到720720.
如果我将x87 FPU控制字的相同舍入和精度控制字段设置exp()为如下fistpl 4(%esp)指令的持续时间:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fldcw 30(%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
Run Code Online (Sandbox Code Playgroud)
我也好720720.
由此我得出的结论exp()是,13 13并不是精确计算13 1.
可能值得查看它的源代码,__gnu_cxx::__promote_2<__gnu_cxx::__enable_if<(std::__is_arithmetic<int>::__value)&&(std::__is_arithmetic<int>::__value), int>::__type, int>::__type std::pow<int, int>(int, int)看看它是如何设法用整数来搞砸指数的(参见,与C不同,exp()它需要两个ints而不是两个doubles).
但我不会责怪exp()这一点.C++ 11定义float pow(float, float)并 long double pow(long double, long double)补充了C语言double pow(double, double).但double pow(int, int)标准中没有.
编译器为整数参数提供版本的事实不会对结果的精度做出任何额外的保证.如果exp()计算b为
a b = 2 b*log 2(a)
或者作为
a b = e b*ln(a)
对于浮点值,过程中肯定会出现舍入错误.
如果"整数"版本exp()做了类似的事情并且由于舍入错误而导致类似的精度损失,它仍然可以正常工作.即使精度的损失是由于一些愚蠢的错误而不是因为正常的舍入错误,它也会这样做.
不管这种行为看起来多么令人惊讶,这是正确的.或者我相信直到被证明是错误的.
| 归档时间: |
|
| 查看次数: |
1206 次 |
| 最近记录: |