我已經能夠與編譯器的一個版本重現該問題。
礦是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
。
如果我設置的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
我得到720720
爲好。
從這個我只能得出這樣的結論exp()
未計算13 精確地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()
需要兩個ints
,而不是兩個doubles
)。
但我不會責怪exp()
。除C的double pow(double, double)
之外,C++ 11還定義了float pow(float, float)
和long double pow(long double, long double)
。但標準中沒有double pow(int, int)
。
編譯器爲整型參數提供版本的事實不會對結果的精度做任何額外的保證。如果exp()
計算b作爲
一個b = 2 b *表登錄的(a)
或作爲
一個b = e b * ln(a)
對於浮點值,這個過程肯定會有舍入誤差。
如果exp()
的「整數」版本做了類似的操作,並且由於舍入錯誤而導致類似的精度損失,它仍然可以正常工作。即使精度的損失是由於一些愚蠢的錯誤,而不是由於正常的舍入錯誤造成的。
然而令人驚訝的是,這種行爲可能看起來是正確的。或者我相信,直到證明錯誤。
結果如何?什麼是編譯器標誌? – 2013-03-10 06:17:38
另外,什麼是gcc版本?這很可能是一個編譯器錯誤。 我得到正確的答案(720720)與海灣合作委員會4.7.2 – 2013-03-10 06:20:57
你爲什麼寫這樣奇怪的代碼? – Pubby 2013-03-10 06:21:50