È sicuro controllare i valori in virgola mobile per l'uguaglianza su 0?
-
20-08-2019 - |
Domanda
So che non puoi fare affidamento sull'uguaglianza tra valori di tipo doppio o decimale normalmente, ma mi chiedo se 0 è un caso speciale.
Anche se riesco a capire le imprecisioni tra 0,00000000000001 e 0,00000000000002, lo stesso 0 sembra piuttosto difficile da confondere poiché non è proprio niente. Se sei impreciso sul nulla, non è più niente.
Ma non so molto su questo argomento, quindi non spetta a me dirlo.
double x = 0.0;
return (x == 0.0) ? true : false;
Restituirà sempre vero?
Soluzione
È sicuro aspettarsi che il confronto restituisca true
se e solo se la doppia variabile ha un valore esattamente 0.0
(che nello snippet di codice originale è, ovviamente, il caso). Ciò è coerente con la semantica dell'operatore ==
. a == b
significa " a
è uguale a b
" ;.
È non sicuro (perché è non corretto ) aspettarsi che il risultato di alcuni calcoli sarà zero in aritmetica doppia (o più in generale, in virgola mobile) ogni volta che il risultato dello stesso calcolo in matematica pura è zero. Questo perché quando i calcoli entrano nel terreno, appare un errore di precisione in virgola mobile - un concetto che non esiste nell'aritmetica dei numeri reali in matematica.
Altri suggerimenti
Se devi fare molto " uguaglianza " confronti potrebbe essere una buona idea scrivere una piccola funzione di supporto o un metodo di estensione in .NET 3.5 per confrontare:
public static bool AlmostEquals(this double double1, double double2, double precision)
{
return (Math.Abs(double1 - double2) <= precision);
}
Questo potrebbe essere usato nel modo seguente:
double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Per il tuo semplice esempio, quel test va bene. Ma che dire di questo:
bool b = ( 10.0 * .1 - 1.0 == 0.0 );
Ricorda che .1 è un decimale ripetuto in binario e non può essere rappresentato esattamente. Quindi confrontalo con questo codice:
double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );
Ti lascio eseguire un test per vedere i risultati effettivi: è più probabile che tu lo ricordi in questo modo.
Dalla voce MSDN per Double.Equals :
Precisione nei confronti
Il metodo Equals dovrebbe essere usato con attenzione, perché due apparentemente valori equivalenti possono essere ineguali alla diversa precisione dei due valori. Il seguente esempio riporta che il doppio valore .3333 e il Il doppio restituito dividendo 1 per 3 sono disuguale.
...
Anziché confrontare per l'uguaglianza, una tecnica consigliata prevede la definizione di un margine accettabile di differenza tra due valori (come .01% di uno dei valori). Se la valore assoluto della differenza tra i due valori è minore di o uguale a quel margine, la differenza è probabilmente dovuto alle differenze di precisione e, quindi, i valori sono probabilmente uguali. Il seguente esempio usa questa tecnica per confrontare .33333 e 1/3, i due valori Double che ha trovato l'esempio di codice precedente essere disuguale.
Inoltre, vedi Double.Epsilon .
Il problema si presenta quando si confrontano diversi tipi di implementazione del valore in virgola mobile, ad es. confrontando float con double. Ma con lo stesso tipo, non dovrebbe essere un problema.
float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true
Il problema è che il programmatore a volte dimentica che il cast di tipo implicito (double to float) sta accadendo per il confronto e si traduce in un bug.
Se il numero è stato assegnato direttamente al float o al double, allora è sicuro testare contro zero o qualsiasi numero intero che può essere rappresentato in 53 bit per un double o 24 bit per un float.
Oppure, per dirla in un altro modo, puoi sempre assegnare un valore intero a un doppio, quindi confrontare il doppio ritorno con lo stesso numero intero e assicurarti che sarà uguale.
Puoi anche iniziare assegnando un numero intero e fare in modo che i semplici confronti continuino a funzionare attenendosi all'aggiunta, alla sottrazione o alla moltiplicazione per numeri interi (supponendo che il risultato sia inferiore a 24 bit per un float e 53 bit per un doppio) . Quindi puoi trattare float e doppi come numeri interi in determinate condizioni controllate.
No, non è OK. I cosiddetti valori denormalizzati (subnormali), se confrontati con 0,0, comparerebbero come falsi (diversi da zero), ma se usati in un'equazione verrebbero normalizzati (diventano 0,0). Pertanto, l'utilizzo di questo come meccanismo per evitare una divisione per zero non è sicuro. Invece, aggiungi 1.0 e confronta con 1.0. Questo assicurerà che tutti i subnormali siano trattati come zero.
Prova questo e scoprirai che == non è affidabile per double / float.
double d = 0.1 + 0.2;
bool b = d == 0.3;
Ecco il rispondi di Quora.
In realtà, penso che sia meglio usare i seguenti codici per confrontare un doppio valore con 0,0:
double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;
Lo stesso per float:
float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;