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.
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
@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
@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. –