2013-07-03 56 views
1

有一段代碼讓我迷惑,它運行在windows中! 下面是代碼:在windows中傳遞函數的奇怪,浮點型參數

#define point_float2uint(x) *((unsigned int *)&x) 


float divide_1000(float y) 
{ 
    float v = y/1000.0f; 
    return v; 
} 

float divide_1000(int y) 
{ 
    float v = float(y)/1000.0f; 
    return v; 
} 


void float_test(void) 
{ 
    int num[5] = {67975500, 67251500, 67540620, 69435500, 70171500}; 
    for (int i = 0; i < 5; ++i) 
    { 
     int a = num[i]; 
     float af_f = divide_1000(float(a)); 
     float af_i = divide_1000((a)); 
     printf("src num:%d, af_f:%f, %x, af_i:%f, %x\n", num[i], af_f, point_float2uint(af_f), af_i, point_float2uint(af_i)); 
    } 
} 

這裏是輸出,通過VS2005編譯:

src num:67975500, af_f:67975.507813, 4784c3c1, af_i:67975.500000, 4784c3c0 
src num:67251500, af_f:67251.507813, 478359c1, af_i:67251.500000, 478359c0 
src num:67540620, af_f:67540.625000, 4783ea50, af_i:67540.617188, 4783ea4f 
src num:69435500, af_f:69435.507813, 47879dc1, af_i:69435.500000, 47879dc0 
src num:70171500, af_f:70171.507813, 47890dc1, af_i:70171.500000, 47890dc0 

的問題是:爲什麼我使用 「divide_1000」,得到了不同的結果在窗口?這不是我想要的! 我發現並非所有的整數都有不同的結果,但有些就像上面的代碼一樣。

這裏是輸出,通過gcc4.4.5在Debian的comipled:

src num:67975500, af_f:67975.507812, 4784c3c1, af_i:67975.507812, 4784c3c1 
src num:67251500, af_f:67251.507812, 478359c1, af_i:67251.507812, 478359c1 
src num:67540620, af_f:67540.625000, 4783ea50, af_i:67540.625000, 4783ea50 
src num:69435500, af_f:69435.507812, 47879dc1, af_i:69435.507812, 47879dc1 
src num:70171500, af_f:70171.507812, 47890dc1, af_i:70171.507812, 47890dc1 

我得到期運用不同的功能相同的結果 「divide_1000」。這就是我想要的。

+1

請閱讀[每位計算機科學家應瞭解的浮點算術知識](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)。 –

+0

@JoachimPileborg這裏的問題可能是例如爲什麼在其他編譯器中結果不同,他們處理的是哪種優化? – DRC

+0

@Joachim Pileborg謝謝你的幫助。但我想我知道Floating-Point有什麼問題。讓我困惑的是,爲什麼第一種方式和第二種方式有不同的結果? 「每位計算機科學家應該知道什麼是浮點運算」有答案嗎? – hdbean

回答

3

這裏涉及到很多代碼生成設置會影響結果。當使用「經典」FPU指令進行浮點計算時,您報告的差異在默認浮點模型(即「精確」模型)下的非優化代碼中可觀察到。

編譯器將字面翻譯爲第一個調用:原始整數值首先轉換爲float - 存儲在內存中的4字節浮點值(作爲函數參數)。此轉換將值舍入爲+6.7975504e+7,這已不準確。之後float的值從第一個函數的內存中讀取並用於進一步的計算。

第二次調用將int值傳遞給該函數,該函數直接載入高精度FPU寄存器並用於進一步計算。即使您在第二個函數內指定了從intfloat的明確轉換,編譯器也決定忽略您的請求。該值永遠不會真正轉換爲float,這意味着前述的精度損失永遠不會發生。

這是什麼導致你觀察到的差異。

如果你重寫你的第二個功能

float divide_1000(int y) 
{ 
    float fy = y; 
    float v = fy/1000.0f; 
    return v; 
} 

即補充說,float值保存到內存中的指定位置額外的步驟,編譯器將執行非優化代碼的一步。這將導致結果相同。

同樣,上述內容適用於編譯時沒有優化的代碼,當編譯器通常試圖非常接近地轉換所有語句時(但並不總是)。在優化的代碼中,編譯器在兩種情況下都將「不必要的」中間轉換消除爲float以及所有「不必要」的中間存儲器存儲,產生相同的結果。

您可能還想嘗試其他浮點模型(即「嚴格」和「快速」)以查看它如何影響結果。這些浮點模型專門用於處理您觀察到的問題。

如果更改編譯器的代碼生成設置並使其使用SSE指令進行浮點運算,則結果可能也會發生變化(在我的實驗中,使用SSE2指令集而不是FPU指令時差異消失)。

+0

感謝您的回答。我使用'unsigned int mode_87 = 0; int ret = __control87_2(0,0,&mode_87,0); ret = __control87_2(_RC_NEAR,_MCW_RC,&mode_87,0); ret = __control87_2(_PC_24,_MCW_PC,&mode_87,0); '來控制精度。 MSVC是否仍然使用FPU寄存器? – hdbean

+0

是的,你展示給我的方式是有效的! – hdbean

+0

@hdbean:默認情況下,MSVC將使用FPU指令和FPU寄存器來執行浮點計算。但是,如果您進入項目設置 - > C/C++ - >代碼生成並將「啓用增強指令集」設置爲「SSE2」,則編譯器將切換到處理浮點計算的SSE指令。在這種情況下,結果可能會改變。 – AnT