2013-03-19 16 views
9

Dato questo esempio C++ frammento di codice:Perché queste due varianti di codice producono risultati in virgola mobile diversi?

void floatSurprise() 
{ 
    // these come from some sort of calculation 
    int a = 18680, b = 3323524, c = 121; 
    float m = float(a)/c; 

    // variant 1: calculate result from single expression 
    float r1 = b - (2.0f * m * a) + (m * m * c); 
    cout << "r1 = " << r1 << endl; 

    // variant 2: break up the expression into intermediate parts, 
    /// then calculate 
    float 
     r2_p1 = 2.0f * m * a, 
     r2_p2 = m * m * c, 
     r2 = b - r2_p1 + r2_p2; 

    cout << "r2 = " << r2 << endl; 
} 

Il risultato è:

dev1 = 439.703
DEV2 = 439702

Se vista nel debugger, i valori sono effettivamente 439702.50 e 439702.25, rispettivamente, che è di per sé interessante - non sono sicuro del motivo per cui iostream stampa i float senza la parte frazionaria di default. MODIFICA: Il motivo per questo era che l'impostazione predefinita di precisione per cout era troppo bassa, cout necessaria < < setprecision (7) almeno per visualizzare il punto decimale per i numeri di questa grandezza.

Ma sono ancora più interessato al motivo per cui ottengo risultati diversi. Suppongo che abbia a che fare con arrotondamenti e qualche sottile interazione di interi con il tipo di output float richiesto, ma non riesco a metterci il dito sopra. Quale valore è corretto?

Mi sono stupito che fosse così facile spararmi ai piedi con un pezzo di codice così semplice. Qualsiasi intuizione sarà molto apprezzata! Il compilatore era VC++ 2010.

EDIT2: ho fatto un po 'di indagine utilizzando un foglio di calcolo per generare valori di "correggere" per le variabili intermedie e trovato (via tracing) che in effetti venivano tagliati, contribuendo alla perdita di precisione nel risultato finale. Ho anche trovato un problema con la sola espressione, perché in realtà ho usato una pratica funzione per il calcolo dei quadrati invece di m * m lì:

template<typename T> inline T sqr(const T &arg) { return arg*arg; } 

Anche se ho chiesto gentilmente, il compilatore a quanto pare non ha inline questo, e calcolato il valore a parte, tagliando il risultato prima di restituire il valore all'espressione, distorcendo il risultato ancora una volta. Ahia.

+1

Non è una risposta alla tua domanda, ma dovresti preferire "double' su' float' dato che la precisione è spesso molto migliore. Otterrai sempre idiosincrasie come queste (questo è il dolore con l'aritmetica in virgola mobile) ma l'errore sarà molto meno significativo. – syam

+0

@syam: Sto provando a vettorizzare del codice usando SSE quindi ho bisogno di gestire i float per il momento - voglio essere in grado di elaborare 4 valori anziché 2. Inoltre, i calcoli di AFAIR, FP sull'hardware x86 recente sono fatti utilizzando registri nativi a 84 bit indipendentemente da float/double, il taglio avviene alla fine. – neuviemeporte

+0

@neuviemeporte: Hai appena risposto alla tua stessa domanda in quel commento. Il taglio avviene alla fine * tranne quando non *. Il taglio può avvenire * ovunque lungo la linea * in base al capriccio del compilatore. –

risposta

11

Si consiglia di leggere la mia lunga, lunga risposta sul perché la stessa cosa accade in C#:

(.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why?

Riassumendo: prima di tutto, si ottiene solo circa sette cifre decimali di precisione con galleggiante. La risposta corretta è che l'hai fatto con l'aritmetica esatta durante l'intero calcolo è circa 439702.51239669 ... quindi ti stai avvicinando alla risposta corretta considerando le limitazioni di un float, in entrambi i casi.

Ma questo non spiega perché si ottengono risultati diversi con quelli che sembrano esattamente gli stessi calcoli. La risposta è: al compilatore è consentita un'ampia latitudine per rendere la matematica più accurata, e apparentemente si sono imbattuti in due casi in cui l'ottimizzatore prende ciò che è logicamente la stessa espressione e non li ottimizza nello stesso codice.

In ogni caso, leggere attentamente la mia risposta relativa a C#; tutto ciò che si trova qui vale anche per il C++.

+0

Grazie per il valore .512 ... - è diventato paranoico e non si fidava nemmeno della calcolatrice di Windows. Non sono sicuro che dovrei fidarmi di te, vieni a pensarci ....;) – neuviemeporte

Problemi correlati