2012-11-12 81 views
16

我在處理浮點算術問題時遇到了一些令人困惑的事情。爲什麼GDB不同於C++評估浮點運算?

首先,代碼。我蒸餾我的問題的精髓融入這個例子:

#include <iostream> 
#include <iomanip> 

using namespace std; 
typedef union {long long ll; double d;} bindouble; 

int main(int argc, char** argv) { 
    bindouble y, z, tau, xinum, xiden; 
    y.d = 1.0d; 
    z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053 
    tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838 
    // xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a) 
    xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1); 
    // xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b) 
    xiden.d = z.d * (1 - tau.d); 
    cout << hex << xinum.ll << endl << xiden.ll << endl; 
} 

xinumxiden應具有相同的價值,但由於浮點舍入誤差的(y == 1時)他們沒有。我得到的那部分。

問題來了,當我通過GDB運行這段代碼(實際上,我的真正的程序),以追查的差異。如果我使用GDB重現代碼進行的評價工作,它給了xiden一個不同的結果:

$ gdb mathtest 
GNU gdb (Gentoo 7.5 p1) 7.5 
... 
This GDB was configured as "x86_64-pc-linux-gnu". 
... 
(gdb) break 16 
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16. 
(gdb) run 
Starting program: /home/diazona/tmp/mathtest 
... 
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16 
16   cout << hex << xinum.ll << endl << xiden.ll << endl; 
(gdb) print xiden.d 
$1 = 0.16249854626123725 
(gdb) print z.d * (1 - tau.d) 
$2 = 0.16249854626123722 

你會發現,如果我問GDB計算z.d * (1 - tau.d),它給0.16249854626123722(0x3fc4ccc09aeb769a),而在程序中計算相同內容的實際C++代碼給出了0.16249854626123725(0x3fc4ccc09aeb769b)。所以GDB必須使用不同的評估模型來進行浮點運算。任何人都可以對此有所瞭解嗎? GDB的評估與我的處理器評估有何不同?

我看過this related question詢問GDB評估sqrt(3)爲0,但這不應該是一回事,因爲這裏沒有涉及函數調用。

回答

2

它不是GDB VS處理器,它的內存 VS處理器。 x64處理器存儲的精度比存儲器實際保存的要多(80比64比特)。只要它保留在CPU和寄存器中,它就保留了80比特的精度,但是當它被髮送到內存時將決定何時以及如何四捨五入。如果GDB將所有間歇計算結果發送出CPU(我不知道這是否是這種情況,或者任何關閉的情況),它將在步驟處執行四捨五入爲,這會導致略有不同的結果。

5

可能是因爲x86 FPU在寄存器中80位的精度工作,但輪到64位時的值被存儲到存儲器中。 GDB將在(解釋)計算的每一步中存儲到內存中。

+0

實際上,gdb的結果在數學上是更正確的,所以看起來好像gdb使用FPU的更高精度,而g ++可能使用SSE指令。 –

4

GDB的運行時表達式評估系統肯定是不能保證執行相同的有效機器代碼爲您的浮點操作如通過編譯器生成的優化和重新排序的機器代碼,以計算相同的符號表達式的結果。事實上,保證不執行相同的機器代碼來計算給定表達式z.d * (1 - tau.d)的值,因爲這可能被認爲是你的程序的一個子集,在運行時在某些任意的「符號正確的」辦法。

由於優化(替換,重新排序,子表達式消除等),浮點代碼生成和CPU輸出的實現特別容易與其他實現(例如運行時表達式求值器)產生符號不一致,的指令,寄存器分配的選擇以及浮點環境。如果你的代碼片段在臨時表達式中包含很多自動變量(與你的代碼一樣),代碼生成具有特別大的自由度,即使是零優化傳遞也是如此,並且有了這種自由度,在這種情況下,最不重要的一點看起來不一致。

對於爲什麼GDB的運行時評估程序執行它沒有深入瞭解GDB源代碼,構建設置以及它自己的編譯時生成代碼的指令,您將不會得到太多的瞭解。

您可以在生成的程序集中爲您的程序達到最高點,以瞭解最終存儲到z,tau和[相反] xiden的工作方式。通向這些商店的浮點操作的數據流可能不像看起來那樣。

通過禁用所有編譯器優化(例如,GCC上的-O0)並重寫浮點表達式以使用非臨時/自動變量,可以更簡單地嘗試使代碼生成更具確定性。然後打破GDB的每一行並進行比較。

我希望我能夠準確地告訴你爲什麼尾數的最低有效位被翻轉了,但事實是,處理器甚至不知道爲什麼某些東西攜帶了一點而其他東西不是由於例如,沒有完整的指令和代碼和GDB本身的數據跟蹤的評估順序。