Perché il confronto tra double e float porta a risultati inaspettati?

Possibile duplicato:
strano risultato in comparazione di float con letterale float

float f = 1.1; double d = 1.1; if(f == d) // returns false! 

Perché è così?

I fattori importanti presi in considerazione con numeri float o double sono:
Precisione e arrotondamenti


Precisione:
La precisione di un numero in virgola mobile è il numero di cifre che può rappresentare senza perdere alcuna informazione che contiene.

Considera la frazione 1/3 . La rappresentazione decimale di questo numero è 0.33333333333333… con 3 in uscita all’infinito. Un numero di lunghezza infinito richiederebbe che la memoria infinita fosse rappresentata con precisione esatta, ma i tipi di dati float o double generalmente hanno solo 4 o 8 byte. In questo modo, i virgola mobile e i numeri doppi possono memorizzare solo un certo numero di cifre, e il resto è destinato a perdersi. Pertanto, non esiste un modo preciso preciso di rappresentare numeri float o doppi con numeri che richiedono una precisione maggiore di quanto le variabili possano contenere.


Arrotondamento:
Esiste una differenza non ovvia tra i numeri binary e decimal (base 10) .
Considera la frazione 1/10 . In decimal , questo può essere facilmente rappresentato come 0.1 e 0.1 può essere pensato come un numero facilmente rappresentabile. Tuttavia, in binario, 0.1 è rappresentato dalla sequenza infinita: 0.00011001100110011…

Un esempio:

 #include  int main() { using namespace std; cout < < setprecision(17); double dValue = 0.1; cout << dValue << endl; } 

Questo risultato è:

 0.10000000000000001 

E non

 0.1. 

Questo perché il doppio ha dovuto troncare l'approssimazione a causa della sua memoria limitata, che si traduce in un numero che non è esattamente 0.1 . Tale scenario è chiamato errore di arrotondamento .


Ogni volta che si confrontano due float ravvicinati e numeri doppi tali errori di arrotondamento entrano in gioco e alla fine il confronto produce risultati errati e questo è il motivo per cui non si dovrebbe mai confrontare i numeri in virgola mobile o raddoppiare usando == .

Il meglio che puoi fare è prendere la loro differenza e controllare se è inferiore a un epsilon.

 abs(x - y) < epsilon 

Prova a eseguire questo codice, i risultati renderanno la ragione ovvia.

 #include  #include  int main() { std::cout < < std::setprecision(100) << (double)1.1 << std::endl; std::cout << std::setprecision(100) << (float)1.1 << std::endl; std::cout << std::setprecision(100) << (double)((float)1.1) << std::endl; } 

Il risultato:

 1.100000000000000088817841970012523233890533447265625 1.10000002384185791015625 1.10000002384185791015625 

floatdouble possono rappresentare esattamente 1.1. Quando si tenta di fare il confronto il numero float viene convertito implicitamente in un doppio. Il doppio tipo di dati può rappresentare con precisione il contenuto del float, quindi il confronto risulta falso.

In generale, non devi confrontare i float con i float, i double in double o i float con i double usando == .

La migliore pratica è quella di sottrarli e verificare se il valore assoluto della differenza è inferiore a un epsilon piccolo.

 if(std::fabs(f - d) < std::numeric_limits::epsilon()) { // ... } 

Una ragione è perché i numeri in virgola mobile sono (più o meno) le frazioni binarie e possono solo approssimare molti numeri decimali. Molti numeri decimali devono necessariamente essere convertiti in ripetuti “decimali” binari o numeri irrazionali. Ciò introdurrà un errore di arrotondamento.

Da Wikipedia :

Ad esempio, 1/5 non può essere rappresentato esattamente come un numero in virgola mobile usando una base binaria ma può essere rappresentato esattamente usando una base decimale.

Nel tuo caso particolare, un float e double avranno arrotondamenti diversi per la frazione irrazionale / ripetitiva che deve essere usata per rappresentare 1.1 in binario. Ti verrà difficile ottenere che siano “uguali” dopo che le conversioni corrispondenti hanno introdotto diversi livelli di errore di arrotondamento.

Il codice che ho dato sopra risolve questo semplicemente controllando se i valori si trovano entro un delta molto breve. Il tuo confronto cambia da “questi valori sono uguali?” a “questi valori si trovano entro un piccolo margine di errore l’uno dall’altro?”

Inoltre, vedi questa domanda: qual è il modo più efficace per il float e il doppio confronto?

Ci sono anche molte altre stranezze sui numeri in virgola mobile che interrompono un semplice confronto di uguaglianza. Controlla questo articolo per una descrizione di alcuni di essi:

http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

Il float IEEE 754 a 32 bit può memorizzare: 1.1000000238...
La double 64 bit IEEE 754 può memorizzare: 1.1000000000000000888...

Guarda perché non sono “uguali”?


In IEEE 754, le frazioni sono memorizzate in potenze di 2:

 2^(-1), 2^(-2), 2^(-3), ... 1/2, 1/4, 1/8, ... 

Ora abbiamo bisogno di un modo per rappresentare 0.1 . Questa è (una versione semplificata di) la rappresentazione IEEE 754 a 32 bit (float):

 2^(-4) + 2^(-5) + 2^(-8) + 2^(-9) + 2^(-12) + 2^(-13) + ... + 2^(-24) + 2^(-25) + 2^(-27) 00011001100110011001101 1.10000002384185791015625 

Con il double 64 bit, è ancora più preciso. Non si ferma a 2^(-25) , continua a funzionare per il doppio. ( 2^(-48) + 2^(-49) + 2^(-51) , forse?)


risorse

Convertitore IEEE 754 (32 bit)

I galleggianti e i doppi sono memorizzati in un formato binario che non può rappresentare esattamente ogni numero (è imansible rappresentare infinitamente molti numeri diversi possibili in uno spazio finito).

Di conseguenza fanno l’arrotondamento. Il Float deve arrotondare più del doppio, perché è più piccolo, quindi 1.1 arrotondato al più vicino Float valido è diverso da 1.1 arrotondato al più vicino doppio valud.

Per vedere quali numeri sono validi, i float e i double vedono il Floating Point