2012-05-23 14 views
8

Nel seguente codice, le funzioni foo1, foo2 e foo3 sono intese come equivalenti. Tuttavia, quando eseguire foo3 non termina dal ciclo, c'è un motivo per cui questo è il caso?Calcoli a virgola mobile IEEE-754, uguaglianza e restringimento

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; 
} 

Nota: questo è stato compilato utilizzando VS2010 con/fp preciso in modalità di rilascio. Non sono sicuro di come GCC ecc. Possa trattare questo codice, qualsiasi informazione sarebbe ottima. Questo potrebbe essere un problema in cui in foo3, i valori x e x + 1 diventano in qualche modo NaN?

+3

Interessante. Tutte e tre le funzioni terminano come previsto su gcc 4.2.1. Sono tentato di chiamarlo un bug in VS. – ComicSansMS

+3

Hmm. Puzza di ottimizzazione eccessiva (ad es. Un bug del compilatore) per me. –

+2

@MarkDickinson: blocco per me in una build di debug in VS2010 –

risposta

13

Quello che succede è molto probabilmente il seguente. Nell'arco di x86, i calcoli intermedi possono essere eseguiti con 80 bit di precisione (il doppio lungo è il tipo C/C++ corrispondente). Il compilatore usa tutti gli 80 bit per l'operazione (+1) e per l'operazione (! =), Ma tronca i risultati prima della memorizzazione.

Così che il compilatore fa davvero è questo:

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

Questo è assolutamente non-IEEE-conforme e provoca mal di testa senza fine per tutti, ma questo è il valore predefinito per MSVC. Utilizzare il flag di compilatore /fp:strict per disabilitare questo comportamento.

Questo è il mio ricordo del problema di circa 10 anni fa, quindi per favore perdonami se questo non è completamente corretto. See this for the official Microsoft documentation.

EDIT Sono stato molto sorpreso di apprendere che g ++ mostra per impostazione predefinita esattamente lo stesso comportamento (su linux i386, ma non con e.g. -mfpmath = sse).

+4

+1. Usare semplicemente 'double' piuttosto che' long double' sarebbe sufficiente per causare problemi qui, ma IIRC MS adora usare la FPU x87 anche per i build a 64-bit, quindi 'double double' sembra più probabile. –

+1

Ottima risposta! grazie. –

+1

Ottima risposta esperta ma noto che su VS2008 anche con '/ fp: strict' foo3() non termina ancora? – acraig5075

Problemi correlati