Float Oltre promosso a raddoppiare?
-
12-09-2019 - |
Domanda
Ho avuto un piccolo momento WTF questa mattina. Ths WTF si può riassumere con questo:
float x = 0.2f;
float y = 0.1f;
float z = x + y;
assert(z == x + y); //This assert is triggered! (Atleast with visual studio 2008)
Il motivo sembra essere che il x + y
espressione è promosso a raddoppiare e confrontata con la versione troncata in z
. (Se cambio z
al double
l'asserzione non viene attivato).
Vedo che per motivi di precisione avrebbe senso per eseguire tutte aritmetica in virgola mobile a doppia precisione prima di convertire il risultato a singola precisione. Ho trovato il seguente paragrafo nella norma (che immagino io sorta di già sapevo, ma non in questo contesto):
4.6.1.
"An rvalue di tipo float
può essere convertito in un rvalue di tipo double
. Il valore è invariato"
La mia domanda è, è x + y
garantito per essere promosso a raddoppiare o è a discrezione del compilatore?
UPDATE: Dal momento che molte persone hanno sostenuto che non si dovrebbe usare per ==
in virgola mobile, volevo solo dire che nel caso specifico con cui sto lavorando, un confronto esatto è giustificata.
virgola mobile confronto è 'complicato, ecco un interessante sul tema che credo non è stata menzionata.
Soluzione
Non si può in genere presumere che ==
funzionerà come previsto per il galleggiamento tipi di punti. Confrontare i valori arrotondati o utilizzare costrutti come abs(a-b) < tolerance
invece.
La promozione è interamente a discrezione del compilatore (e dipenderà hardware di destinazione, livello di ottimizzazione, ecc).
Che cosa sta succedendo in questo caso particolare è quasi certamente che i valori sono memorizzati in FPU registra ad una precisione superiore a quella in memoria - in generale, moderno hardware FPU funziona con doppio o superiore precisione internamente qualunque sia la precisione il programmatore ha chiesto, con la codice generatrice compilatore per effettuare le conversioni appropriate quando i valori vengono memorizzati nella memoria; in una build unoptimised, il risultato di x+y
è ancora in un registro a punto il confronto viene fatto ma z
sarà stato memorizzato ad memoria e recuperato indietro, e quindi troncato a galleggiare precisione.
Altri suggerimenti
Il progetto di lavoro per la prossima serie C ++ 0x sezione 5, punto 11, dice
I valori degli operandi galleggianti e dei risultati delle espressioni galleggianti possono essere rappresentati in maggior precisione e la gamma di quello richiesto dal tipo; i tipi non sono cambiati così
Quindi, a discrezione del compilatore.
Utilizzando gcc 4.3.2, l'affermazione è non innescata, e in effetti, il rvalue tornato da x + y
è un float
, piuttosto che un double
.
Quindi spetta al compilatore. Questo è il motivo per cui non è mai saggio fare affidamento sulla parità esatta tra due valori in virgola mobile.
Il ++ FAQ lite C ha qualche ulteriore discussione sul tema:
E 'il problema dal numero di float di conversione binaria non dà la precisione accurata.
E all'interno sizeof(float)
byte cant accogliere valore preciso del numero di galleggiante e operazione aritmetica può portare ad approssimazione e quindi uguaglianza fallisce.
Vedi sotto per es.
float x = 0.25f; //both fits within 4 bytes with precision
float y = 0.50f;
float z = x + y;
assert(z == x + y); // it would work fine and no assert
Vorrei pensare che sarebbe a discrezione del compilatore, ma si potrebbe sempre forzo con un cast se era il vostro pensiero?
Ancora un altro motivo per non confrontare direttamente i galleggianti.
if (fabs(result - expectedResult) < 0.00001)