È sicuro controllare che i valori in virgola mobile siano uguali a 0?


100

So che normalmente non puoi fare affidamento sull'uguaglianza tra valori di tipo double o decimale, ma mi chiedo se 0 sia un caso speciale.

Anche se posso capire le imprecisioni tra 0,00000000000001 e 0,00000000000002, lo stesso 0 sembra piuttosto difficile da rovinare poiché non è niente. Se sei impreciso su niente, non è più niente.

Ma non so molto di questo argomento, quindi non sta a me dirlo.

double x = 0.0;
return (x == 0.0) ? true : false;

Tornerà sempre vero?


69
L'operatore ternario è ridondante in quel codice :)
Joel Coehoorn

5
LOL hai ragione. Go me
Gene Roberts,

Non lo farei perché non sai come x è stato impostato a zero. Se vuoi ancora farlo, probabilmente vuoi arrotondare o x pavimento per sbarazzarti di 1e-12 o simili che potrebbero essere etichettati alla fine.
Rex Logan,

Risposte:


115

E ' sicura di aspettarsi che il confronto tornerà truese e solo se la doppia variabile ha un valore di esattamente 0.0(che nel frammento di codice originale è, ovviamente, il caso). Ciò è coerente con la semantica ==dell'operatore. a == bsignifica " aè uguale a b".

Non è sicuro (perché non è corretto ) aspettarsi che il risultato di un calcolo sia zero nell'aritmetica doppia (o più in generale, virgola mobile) ogni volta che il risultato dello stesso calcolo in matematica pura è zero. Questo perché quando i calcoli vengono a galla, appare un errore di precisione in virgola mobile, un concetto che non esiste nell'aritmetica dei numeri reali in matematica.


51

Se hai bisogno di fare molti confronti di "uguaglianza", 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);

4
Potresti avere un errore di cancellazione sottrattiva confrontando double1 e double2, nel caso in cui questi numeri abbiano valori molto vicini tra loro. Rimuoverei Math.Abs ​​e controllerei ogni ramo individualmente d1> = d2 - e e d1 <= d2 + e
Theodore Zographos

"Poiché Epsilon definisce l'espressione minima di un valore positivo il cui intervallo è vicino allo zero, il margine di differenza tra due valori simili deve essere maggiore di Epsilon. In genere, è molte volte maggiore di Epsilon. Per questo motivo, ti consigliamo di farlo non utilizzare Epsilon quando si confrontano i valori Double per l'uguaglianza. " - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
Rafael Costa

15

Per il tuo semplice campione, 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.


5
In realtà, questo restituisce true per qualche motivo (almeno in LINQPad).
Alexey Romanov,

Qual è il "problema .1" di cui parli?
Teejay

14

Dalla voce MSDN per Double.Equals :

Precisione nei confronti

Il metodo Equals deve essere utilizzato con cautela, poiché due valori apparentemente equivalenti possono essere disuguali a causa della diversa precisione dei due valori. L'esempio seguente riporta che il valore Double .3333 e il Double restituito dividendo 1 per 3 non sono uguali.

...

Piuttosto che confrontare per l'uguaglianza, una tecnica consigliata implica la definizione di un margine di differenza accettabile tra due valori (come lo 0,01% di uno dei valori). Se il valore assoluto della differenza tra i due valori è inferiore o uguale a tale margine, è probabile che la differenza sia dovuta a differenze di precisione e, pertanto, è probabile che i valori siano uguali. Nell'esempio seguente viene utilizzata questa tecnica per confrontare .33333 e 1/3, i due valori Double che nell'esempio di codice precedente erano diversi.

Inoltre, vedere Double.Epsilon .


1
È anche possibile che valori non del tutto equivalenti vengano confrontati come uguali. Ci si aspetterebbe che se x.Equals(y), allora (1/x).Equals(1/y), ma non è così se xè 0ed yè 1/Double.NegativeInfinity. Questi valori si dichiarano uguali, anche se i loro reciproci non lo fanno.
supercat

@supercat: sono equivalenti. E non hanno reciproci. Potresti eseguire di nuovo il test con x = 0e y = 0, e lo troverai comunque 1/x != 1/y.
Ben Voigt

@ BenVoigt: con xe ycome tipo double? Come si confrontano i risultati per renderli non uguali? Nota che 1 / 0.0 non è NaN.
supercat il

@supercat: Ok, è una delle cose che IEEE-754 sbaglia. (In primo luogo, 1.0/0.0non è NaN come dovrebbe essere, poiché il limite non è univoco. In secondo luogo, gli infiniti si confrontano tra loro uguali, senza prestare alcuna attenzione ai gradi di infinito)
Ben Voigt

@BenVoigt: Se lo zero era il risultato della moltiplicazione di due numeri molto piccoli, la divisione di 1.0 in quello dovrebbe produrre un valore che confronta maggiore di qualsiasi numero dei piccoli numeri aveva lo stesso segno e inferiore a qualsiasi numero se uno dei piccoli i numeri avevano segni opposti. IMHO, IEEE-754 sarebbe meglio se avesse uno zero senza segno, ma infinitesimi positivi e negativi.
supercat

6

Il problema nasce quando si confrontano diversi tipi di implementazione di valori in virgola mobile, ad esempio 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 il risultato è un bug.


3

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.

O, in altre parole, puoi sempre assegnare un valore intero a un double e quindi confrontare il double con lo stesso numero intero e ti verrà garantito che sarà uguale.

Puoi anche iniziare assegnando un numero intero e fare in modo che semplici confronti continuino a funzionare attenendoti 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 raddoppia come numeri interi in determinate condizioni controllate.


Sono d'accordo con la tua affermazione in generale (e l'ho votata per favore) ma credo che dipenda davvero se l'implementazione in virgola mobile IEEE 754 viene utilizzata o meno. E credo che ogni computer "moderno" utilizzi IEEE 754, almeno per l'archiviazione dei float (ci sono strane regole di arrotondamento che differiscono).
Mark Lakata

2

No, non va bene. I cosiddetti valori denormalizzati (subnormali), se paragonati a 0,0, sarebbero falsi (diversi da zero), ma se usati in un'equazione sarebbero normalizzati (diventerebbero 0,0). Pertanto, l'utilizzo di questo come meccanismo per evitare una divisione per zero non è sicuro. Aggiungere invece 1.0 e confrontare con 1.0. Ciò garantirà che tutti i subnormali vengano trattati come zero.


I subnormali sono anche conosciuti come denormali
Manuel

I subnormali non diventano uguali a zero quando vengono utilizzati, sebbene possano o meno produrre lo stesso risultato a seconda dell'operazione esatta.
wnoise

-2

Prova questo e scoprirai che == non è affidabile per double / float.
double d = 0.1 + 0.2; bool b = d == 0.3;

Ecco la risposta di Quora.


-4

In realtà, penso che sia meglio usare i seguenti codici per confrontare un valore doppio 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;

5
No. Dai documenti di double.Epsilon: "Se crei un algoritmo personalizzato che determina se due numeri in virgola mobile possono essere considerati uguali, devi utilizzare un valore maggiore della costante Epsilon per stabilire il margine di differenza assoluto accettabile affinché i due valori siano considerati uguali. (In genere, quel margine di differenza è molte volte maggiore di Epsilon.) "
Alastair Maw

1
@AlastairMaw questo si applica al controllo di due doppie di qualsiasi dimensione per l'uguaglianza. Per controllare l'uguaglianza a zero, double.Epsilon va bene.
jwg

4
No, non lo è . È molto probabile che il valore a cui si è arrivati ​​tramite un calcolo sia molte volte epsilon lontano da zero, ma dovrebbe comunque essere considerato zero. Non ottieni magicamente un intero gruppo di precisione extra nel tuo risultato intermedio da qualche parte, solo perché sembra essere vicino allo zero.
Alastair Maw

4
Ad esempio: (1.0 / 5.0 + 1.0 / 5.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0) <double.Epsilon == false (e considerevolmente così in termini di magnitudo: 2.78E-17 vs 4.94E -324)
Alastair Maw

allora, qual è la precisione consigliata, se double.Epsilon non va bene? 10 volte di epsilon andrebbero bene? 100 volte?
liang
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.