2013-03-10 52 views
10

最近,我寫了一個小程序,並使用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; 
} 

大會輸出(4.5.2):

http://pastebin.com/EJAkVAaH

大會輸出(4.7.0):

http://pastebin.com/kzbbFGs6

+0

結果如何?什麼是編譯器標誌? – 2013-03-10 06:17:38

+0

另外,什麼是gcc版本?這很可能是一個編譯器錯誤。 我得到正確的答案(720720)與海灣合作委員會4.7.2 – 2013-03-10 06:20:57

+1

你爲什麼寫這樣奇怪的代碼? – Pubby 2013-03-10 06:21:50

回答

9

我已經能夠與編譯器的一個版本重現該問題。

礦是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()的「整數」版本做了類似的操作,並且由於舍入錯誤而導致類似的精度損失,它仍然可以正常工作。即使精度的損失是由於一些愚蠢的錯誤,而不是由於正常的舍入錯誤造成的。

然而令人驚訝的是,這種行爲可能看起來是正確的。或者我相信,直到證明錯誤。

+0

非常感謝!它幫助我很多。 – 2013-03-10 12:38:33