我已经能够用单个编译器版本重现这个问题。
我的是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
正如您所见,该值在编译时进行计算。
当我使用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
如果我将调用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
我得到了 720720
。
如果我为 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
我也得到了 720720
。
从这里我只能得出结论,exp()
没有将 131 精确计算为 13.0。
可能值得查看那个 __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()
不同,它需要两个 int
而不是两个 double
)。
但我不会因此而责怪 exp()
。C++11 除了 C 的 double pow(double, double)
之外还定义了 float pow(float, float)
和 long double pow(long double, long double)
。但标准中没有 double pow(int, int)
。
编译器提供一个整数参数版本并不会对结果的精度做出任何额外保证。如果 exp()
计算 ab 为
ab = 2b * log2(a)
或者为
ab = eb * ln(a)
对于浮点数,过程中肯定会有舍入误差。
如果 "整数" 版本的 exp()
做类似的事情并因舍入误差而导致类似的精度损失,它仍然正常工作。即使精度损失是由于一些愚蠢的错误而不是由于正常的舍入误差引起的。
无论这种行为看起来多么奇怪,都是正确的。除非证明我错了。
a = int(0.5 + a*pow(b, 1));
。我猜测pow(b,1)
返回的是12.9999...而不是13。 - comocomocomocomo