2012-05-23 36 views
8

在以下代碼中,函數foo1,foo2和foo3旨在等效。然而,當運行foo3不會從循環中終止時,是否有這種情況的原因?IEEE-754浮點計算,相等和縮小

template <typename T> 
T foo1() 
{ 
    T x = T(1); 
    T y = T(0); 
    for (;;) 
    { 
     if (x == y) break; 
     y = x; 
     ++x; 
    } 
    return x; 
} 

template <typename T> 
T foo2() 
{ 
    T x = T(0); 
    for (;;) 
    { 
     T y = x + T(1); 
     if (!(x != y)) break; 
     ++x; 
    } 
    return x; 
} 

template <typename T> 
T foo3() 
{ 
    T x = T(0); 
    while (x != (x + T(1))) ++x; 
    return x; 
} 

int main() 
{ 
    printf("1 float: %20.5f\n", foo1<float>()); 
    printf("2 float: %20.5f\n", foo2<float>()); 
    printf("3 float: %20.5f\n", foo3<float>()); 
    return 0; 
} 

注意:這是在發佈模式下使用VS2010以/ fp精確編譯的。不知道GCC等會如何處理這些代碼,任何信息都會很棒。難道這是一個問題,在foo3中,x和x + 1的值會以某種方式變成NaN?

+3

有趣的問題。所有三個函數按照預期在gcc 4.2.1上終止。我很想把它稱爲VS中的一個錯誤。 – ComicSansMS

+3

嗯。聞起來像一個過度的優化(即,一個編譯器錯誤)給我。 –

+2

@MarkDickinson:在VS2010中的調試版本中掛起 –

回答

13

發生什麼最有可能是以下情況。在x86 arch上,可以用80位精度完成中間計算(long double是相應的C/C++類型)。編譯器將所有80位用於(+1)操作和(!=)操作,但會在存儲之前截斷結果。

那麼你的編譯器確實是這樣的:

while ((long double)(x) != ((long double)(x) + (long double)(1))) { 
    x = (float)((long double)(x) + (long double)(1)); 
} 

這是絕對非IEEE符合並引起頭痛不已爲大家,但是這是MSVC的默認。使用/fp:strict編譯器標誌來禁用此行爲。

這是我對約10年前的問題的回憶,所以請原諒我,如果這是不完全正確的。 See this for the official Microsoft documentation

編輯我非常驚訝地發現,默認情況下,g ++表現出完全相同的行爲(在i386 linux上,但沒有例如-mfpmath = sse)。

+4

+1。使用'double'而不是'long double'會足以導致問題,但是IIRC MS仍然喜歡使用x87 FPU,即使是64位版本,所以'long double'似乎更有可能。 –

+1

優秀的答案!謝謝。 –

+1

偉大的知識淵博的答案,但我注意到在VS2008甚至與'/ fp:strict' foo3()仍然不會終止? – acraig5075