Il problema è, come suggerito nella domanda, che si sta confrontando un float con un doppio.
C'è un problema più generale con il confronto di float, questo accade perché quando si esegue un calcolo su un numero in virgola mobile il risultato del calcolo potrebbe non essere esattamente quello che ci si aspetta. È abbastanza comune che l'ultimo bit del float risultante sia errato (anche se l'imprecisione può essere maggiore dell'ultimo bit). Se si utilizza ==
per confrontare due float, tutti i bit devono essere uguali per i float. Se il tuo calcolo dà un risultato un po 'impreciso, allora non compareranno uguali quando te lo aspetti. Invece di confrontare i valori come questo, puoi confrontarli per vedere se sono quasi uguali. Per fare questo puoi prendere la differenza positiva tra i float e vedere se è inferiore a un dato valore (chiamato epsilon).
Per scegliere un buon epsilon è necessario capire un po 'sui numeri in virgola mobile. I numeri in virgola mobile funzionano in modo simile a rappresentare un numero per un dato numero di cifre significative. Se lavoriamo a 5 cifre significative e il tuo calcolo risulta errato nell'ultima cifra del risultato, allora 1.2345 avrà un errore di + -0.0001 mentre 1234500 avrà un errore di + -100. Se si basa sempre il margine di errore sul valore 1.2345, la routine di confronto sarà identica a ==
per tutti i valori superiori a 10 (quando si utilizza il decimale). Questo è peggio in binario, sono tutti valori maggiori di 2. Ciò significa che l'epsilon che scegliamo deve essere relativo alla dimensione dei float che stiamo confrontando.
FLT_EPSILON è il divario tra 1 e il successivo movimento più vicino. Ciò significa che può essere un buon epsilon scegliere se il tuo numero è compreso tra 1 e 2, ma se il tuo valore è maggiore di 2 usando questo epsilon è inutile perché il divario tra 2 e il successivo float più vicino è maggiore di epsilon. Quindi dobbiamo scegliere un epsilon in relazione alla dimensione dei nostri float (poiché l'errore nel calcolo è relativo alla dimensione dei nostri float).
Un buon (ish) virgola mobile confronto di routine simile a questa:.
bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier)
{
float epsilon;
/* May as well do the easy check first. */
if (a == b)
return true;
if (a > b) {
epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier;
} else {
epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier;
}
return fabs (a - b) <= epsilon;
}
Questa routine confronto confronta carri relativi alla dimensione del più grande galleggiante passato in scalbnf(1.0f, ilogb(a)) * FLT_EPSILON
trova il divario tra a
e la prossima il galleggiante più vicino. Questo viene quindi moltiplicato per il numero epsilonMultiplier
, quindi la dimensione della differenza può essere regolata, a seconda di quanto sia inaccurato il risultato del calcolo.
È possibile effettuare una semplice compareLessThan
di routine come questa:
bool compareLessThan (float a, float b, unsigned epsilonMultiplier)
{
if (compareNearlyEqual (a, b, epsilonMultiplier)
return false;
return a < b;
}
Si potrebbe anche scrivere un molto simile compareGreaterThan
funzione.
Vale la pena notare che confrontare galleggianti come questo potrebbe non essere sempre quello che vuoi. Ad esempio, questo non troverà mai che un float è vicino a 0 a meno che non sia 0. Per risolvere questo problema, devi decidere quale valore pensavi fosse vicino allo zero e scrivere un test aggiuntivo per questo.
A volte le imprecisioni ottenute non dipenderanno dalla dimensione del risultato di un calcolo, ma dipenderanno dai valori inseriti in un calcolo.Ad esempio, sin(1.0f + (float)(200 * M_PI))
darà un risultato molto meno accurato di sin(1.0f)
(i risultati dovrebbero essere identici). In questo caso la routine di confronto dovrebbe considerare il numero inserito nel calcolo per conoscere il margine di errore della risposta.
Vedere anche, "Quello che ogni computer Scientist dovrebbe sapere aritmetica alla virgola mobile:" http://docs.sun.com/source/ 806-3568/ncg_goldberg.html –
Per quelli di voi (in particolare @alastair) che hanno lavorato per migliorare la mia risposta, non sono convinto che potrebbe essere migliorato. Sono d'accordo che non era corretto e probabilmente pericoloso. L'ho cancellato. Per favore, vedi la risposta di James Snook per un'esplorazione molto più profonda di questo problema non banale. –
@RobNapier Devo dire che ho pensato che valesse la pena mantenere la risposta, con le correzioni aggiunte, ma capisco il tuo punto di vista. Mi scuso se la mia modifica sembrava un po 'aggressiva - volevo solo chiarire quale fosse il problema. – alastair