Cosa c'è di sbagliato nell'usare == per confrontare i float in Java?


177

Secondo questa pagina java.sun == è l'operatore di confronto di uguaglianza per i numeri in virgola mobile in Java.

Tuttavia, quando scrivo questo codice:

if(sectionID == currentSectionID)

nel mio editor ed eseguo analisi statiche, ottengo: "JAVA0078 Valori in virgola mobile rispetto a =="

Cosa c'è di sbagliato nell'utilizzo ==per confrontare i valori in virgola mobile? Qual è il modo corretto di farlo? 


29
Poiché il confronto dei float con == è problematico, non è saggio usarli come ID; i nomi nel tuo codice di esempio suggeriscono che è quello che stai facendo; numeri interi lunghi (long) sono preferiti e lo standard di fatto per gli ID.
Carl Manaster,


4
Sì, è stato solo un esempio casuale o in realtà usi i float come ID? C'è una ragione?
Per Wiklander,

5
"per i campi float, utilizzare il metodo Float.compare; e per i campi doppi, utilizzare Double.compare. Il trattamento speciale dei campi float e double è reso necessario dall'esistenza di Float.NaN, -0.0f e delle analoghe doppie costanti; consultare la documentazione Float.equals per i dettagli. " (Joshua Bloch: Effective Java)
lbalazscs,

Risposte:


211

il modo corretto di testare i float per "uguaglianza" è:

if(Math.abs(sectionID - currentSectionID) < epsilon)

dove epsilon è un numero molto piccolo come 0,00000001, a seconda della precisione desiderata.


26
Vedi il link nella risposta accettata ( cygnus-software.com/papers/comparingfloats/comparingfloats.htm ) per cui un epsilon fisso non è sempre una buona idea. In particolare, poiché i valori nei float confrontati diventano grandi (o piccoli), epsilon non è più appropriato. (L'uso di epsilon va bene se sai che i tuoi valori float sono tutti relativamente ragionevoli.)
PT

1
@PT Può moltiplicare epsilon con un numero e cambiare funzione if(Math.abs(sectionID - currentSectionID) < epsilon*sectionIDper affrontare il problema?
enthusiasticgeek,

3
Questa potrebbe anche essere la risposta migliore finora, ma è ancora imperfetta. Da dove prendi epsilon?
Michael Piefel,

1
@MichaelPiefel dice già: "dipende dalla precisione desiderata". I galleggianti per loro natura sono un po 'come i valori fisici: sei interessato solo a un numero limitato di posizioni a seconda della totale imprecisione, qualsiasi differenza oltre quella è considerata controversa.
ivan_pozdeev,

Ma l'OP voleva davvero solo verificare l'uguaglianza, e poiché questo è noto per essere inaffidabile, deve usare un metodo diverso. Tuttavia, non immagino che sappia qual è la sua "precisione desiderata"; quindi se tutto ciò che desideri è un test di uguaglianza più affidabile, la domanda rimane: da dove prendi epsilon? Ho proposto di utilizzare Math.ulp()nella mia risposta a questa domanda.
Michael Piefel,

53

I valori in virgola mobile possono essere disattivati ​​di un po ', quindi potrebbero non essere riportati esattamente uguali. Ad esempio, impostando un float su "6.1" e quindi stampandolo di nuovo, potresti ottenere un valore riportato di qualcosa come "6.099999904632568359375". Questo è fondamentale per il modo in cui i galleggianti funzionano; pertanto, non si desidera confrontarli usando l'uguaglianza, ma piuttosto il confronto all'interno di un intervallo, ovvero se la differenza del float con il numero con cui si desidera confrontarlo è inferiore a un certo valore assoluto.

Questo articolo sul registro offre una buona panoramica del perché questo è il caso; lettura utile e interessante.


@kevindtimm: quindi farai i tuoi test di uguaglianza in questo modo, quindi se (numero == 6.099999904632568359375) ogni volta che vuoi sapere del numero è uguale a 6.1 ... Sì, hai ragione ... tutto nel computer è strettamente deterministico, solo che le approssimazioni usate per i float sono controintuitive quando si fanno problemi di matematica.
Newtopian,

I valori in virgola mobile non sono deterministicamente imprecisi solo su hardware molto specifico .
Stuart P. Bentley,

1
@Stuart Potrei sbagliarmi, ma non credo che il bug FDIV non sia deterministico. Le risposte fornite dall'hardware non erano conformi alle specifiche, ma erano deterministiche, in quanto lo stesso calcolo produceva sempre lo stesso risultato errato
Gravità,

@Gravity Puoi obiettare che qualsiasi comportamento è deterministico dato uno specifico set di avvertimenti.
Stuart P. Bentley,

I valori in virgola mobile non sono imprecisi. Ogni valore in virgola mobile è esattamente quello che è. Ciò che può essere impreciso è il risultato di un calcolo in virgola mobile . Ma attenzione! Quando vedi qualcosa come 0.1 in un programma, questo non è un valore in virgola mobile. È un valore letterale in virgola mobile --- una stringa che il compilatore converte in un valore in virgola mobile eseguendo un calcolo .
Solomon Slow

22

Giusto per dare il motivo dietro ciò che dicono tutti gli altri.

La rappresentazione binaria di un float è piuttosto fastidiosa.

In binario, la maggior parte dei programmatori conosce la correlazione tra 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d

Bene funziona anche nell'altro modo.

.1b = .5d, .01b = .25d, .001b = .125, ...

Il problema è che non esiste un modo esatto per rappresentare la maggior parte dei numeri decimali come .1, .2, .3, ecc. Tutto ciò che puoi fare è approssimativo in binario. Il sistema esegue un piccolo arrotondamento quando i numeri vengono stampati in modo da visualizzare .1 invece di .10000000000001 o .99999999999999 (che sono probabilmente vicini alla rappresentazione memorizzata come lo è .1)

Modifica dal commento: la ragione per cui questo è un problema sono le nostre aspettative. Ci aspettiamo che i 2/3 vengano sfumati a un certo punto quando li convertiamo in decimali, .7 o .67 o .666667 .. Ma non ci aspettiamo automaticamente che .1 venga arrotondato allo stesso modo di 2/3 - ed è esattamente quello che sta succedendo.

A proposito, se sei curioso il numero che memorizza internamente è una rappresentazione binaria pura usando una "Notazione scientifica" binaria. Quindi se gli dicessi di memorizzare il numero decimale 10.75d, memorizzerebbe 1010b per il 10 e .11b per il decimale. Quindi memorizzerebbe .101011 quindi salva alcuni bit alla fine per dire: spostare il punto decimale di quattro posizioni a destra.

(Anche se tecnicamente non è più un punto decimale, ora è un punto binario, ma quella terminologia non avrebbe reso le cose più comprensibili per la maggior parte delle persone che avrebbero trovato questa risposta di qualsiasi utilità.)


1
@Matt K - um, non punto fisso; se "salvi alcuni bit alla fine per dire sposta i punti del punto decimale [N] a destra", questo è il punto mobile. Il punto fisso prende, per bene, la posizione del punto radix. Inoltre, in generale, poiché è sempre possibile spostare il punto binamale (?) Per lasciarti con un '1' nella posizione più a sinistra, troverai alcuni sistemi che omettono il '1' iniziale, dedicando lo spazio così liberato (1 bit!) per estendere l'intervallo dell'esponente.
JustJeff,

Il problema non ha nulla a che fare con la rappresentazione binaria vs. decimale. Con il virgola mobile decimale, hai ancora cose come (1/3) * 3 == 0.999999999999999999999999999999.
dan04,

2
@ dan04 sì, perché 1/3 non ha una rappresentazione decimale OR binaria, ha una rappresentazione trinaria e si convertirà correttamente in questo modo :). I numeri che ho elencato (.1, .25, ecc.) Hanno tutti rappresentazioni decimali perfette ma nessuna rappresentazione binaria - e le persone sono abituate a coloro che hanno rappresentazioni "esatte". BCD li gestirà perfettamente. Questa è la differenza
Bill K,

1
Questo dovrebbe avere molti più voti, dal momento che descrive il problema REALE alla base del problema.
Levita,

19

Cosa c'è di sbagliato nell'usare == per confrontare i valori in virgola mobile?

Perché non è vero 0.1 + 0.2 == 0.3


7
che dire Float.compare(0.1f+0.2f, 0.3f) == 0?
Aquarius Power il

0.1f + 0.2f == 0.3f ma 0.1d + 0.2d! = 0.3d. Per impostazione predefinita, 0,1 + 0,2 è un doppio. 0.3 è anche un doppio.
burnabyRails,

12

Penso che ci sia molta confusione attorno ai galleggianti (e ai doppi), è bene chiarirlo.

  1. Non c'è nulla di intrinsecamente sbagliato nell'uso dei float come ID nella JVM conforme allo standard [*]. Se si imposta semplicemente l'ID float su x, non fare nulla con esso (cioè senza aritmetica) e successivamente testare y == x, andrà tutto bene. Inoltre non c'è nulla di sbagliato nell'usarli come chiavi in ​​una HashMap. Quello che non puoi fare è assumere uguaglianze come x == (x - y) + y, ecc. Detto questo, le persone di solito usano tipi interi come ID, e puoi osservare che la maggior parte delle persone qui è rimandata da questo codice, quindi per motivi pratici, è meglio aderire alle convenzioni . Nota che ci sono tanti doublevalori diversi quanti sono lunghi values, quindi non ottieni nulla usando double. Inoltre, generare "prossimo ID disponibile" può essere complicato con i doppi e richiede una certa conoscenza dell'aritmetica in virgola mobile. Non vale la pena.

  2. D'altra parte, fare affidamento sull'uguaglianza numerica dei risultati di due calcoli matematicamente equivalenti è rischioso. Ciò è dovuto agli errori di arrotondamento e alla perdita di precisione durante la conversione da rappresentazione decimale a binaria. Questo è stato discusso a morte su SO.

[*] Quando ho detto "JVM conforme agli standard" volevo escludere alcune implementazioni JVM danneggiate dal cervello. Vedere questo .


Quando si usano i float come ID, si deve fare attenzione a assicurarsi che vengano confrontati usando ==piuttosto che a equals, oppure assicurarsi che nessun float che si confronta in modo diseguale venga archiviato in una tabella. Altrimenti, un programma che prova ad esempio a contare quanti risultati unici possono essere prodotti da un'espressione quando vengono alimentati vari input può considerare ogni valore NaN come unico.
supercat,

Quanto sopra si riferisce a Float, non a float.
quant_dev,

Di cosa sta parlando Float? Se si tenta di costruire una tabella di floatvalori univoci e li confronta con ==, le orribili regole di confronto IEEE-754 comporteranno l'inondazione di NaNvalori nella tabella .
supercat

floatil tipo non ha equalsmetodo.
quant_dev

Ah - Non intendevo un equalsmetodo di istanza, ma piuttosto il metodo statico (penso all'interno della Floatclasse) che confronta due valori di tipo float.
supercat

9

Questo è un problema non specifico di Java. L'uso di == per confrontare due float / doppi / qualsiasi numero di tipo decimale può potenzialmente causare problemi a causa del modo in cui sono memorizzati. Un galleggiante a precisione singola (secondo lo standard IEEE 754) ha 32 bit, distribuito come segue:

1 bit - Segno (0 = positivo, 1 = negativo)
8 bit - Esponente (una rappresentazione speciale (bias-127) della x in 2 ^ x)
23 bit - Mantisa. Il numero di attuall che è memorizzato.

La mantisa è ciò che causa il problema. È un po 'come la notazione scientifica, solo il numero nella base 2 (binario) sembra 1.110011 x 2 ^ 5 o qualcosa di simile. Ma in binario, il primo 1 è sempre un 1 (tranne per la rappresentazione di 0)

Pertanto, per risparmiare un po 'di spazio di memoria (gioco di parole), IEEE ha deciso che l'1 dovrebbe essere assunto. Ad esempio, una mantisa di 1011 è davvero 1.1011.

Ciò può causare alcuni problemi con il confronto, specialmente con 0 poiché 0 non può essere rappresentato esattamente in un float. Questo è il motivo principale per cui == è scoraggiato, oltre ai problemi matematici in virgola mobile descritti da altre risposte.

Java ha un problema unico in quanto il linguaggio è universale su molte piattaforme diverse, ognuna delle quali potrebbe avere il proprio formato float unico. Ciò rende ancora più importante evitare ==.

Il modo corretto di confrontare due float (non specifici per lingua) per l'uguaglianza è il seguente:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

dove ACCEPTABLE_ERROR è #defined o qualche altra costante uguale a 0.000000001 o qualunque precisione richiesta, come già menzionato Victor.

Alcune lingue hanno questa funzionalità o questa costante integrata, ma in genere è una buona abitudine.


3
Java ha un comportamento definito per i float. Non dipende dalla piattaforma.
Yishai,

9

Ad oggi, il modo semplice e veloce per farlo è:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

Tuttavia, i documenti non specificano chiaramente il valore della differenza di margine (un epsilon dalla risposta di @Victor) che è sempre presente nei calcoli sui float, ma dovrebbe essere qualcosa di ragionevole in quanto fa parte della libreria di lingue standard.

Tuttavia, se è necessaria una precisione superiore o personalizzata, allora

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

è un'altra opzione di soluzione.


1
I documenti che hai collegato indicano "il valore 0 se f1 è numericamente uguale a f2" che lo rende uguale a quello (sectionId == currentSectionId)che non è preciso per i punti mobili. il metodo di Epsilon è l'approccio migliore, che è in questa risposta: stackoverflow.com/a/1088271/4212710
typoerrpr

8

I valori dei punti di espansione non sono affidabili a causa dell'errore di arrotondamento.

Come tali, probabilmente non dovrebbero essere usati come valori chiave, come sectionID. Utilizzare invece numeri interi o longse intnon contiene abbastanza valori possibili.


2
Concordato. Dato che si tratta di ID, non c'è motivo di complicare le cose con l'aritmetica in virgola mobile.
Yohnny,

2
O un lungo. A seconda di quanti ID univoci verranno generati in futuro, un int potrebbe non essere abbastanza grande.
Wayne Hartman,

Quanto è preciso il doppio rispetto al galleggiante?
Arvindh Mani

1
@ArvindhMani doublesono molto più precisi, ma sono anche valori in virgola mobile, quindi la mia risposta doveva includere sia floate double.
Eric Wilson,

7

Oltre alle risposte precedenti, dovresti essere consapevole che ci sono comportamenti strani associati -0.0fe +0.0f(sono ==ma non equals) e Float.NaNequalsma non== ) (speranza ho capito bene -! Argh, non lo fanno).

Modifica: controlliamo!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

Benvenuti in IEEE / 754.


Se qualcosa è ==, allora sono identici fino al bit. Come potrebbero non essere uguali ()? Forse ce l'hai al contrario?
mkb,

@Matt NaN è speciale. Double.isNaN (double x) in Java è effettivamente implementato come {return x! = X; } ...
quant_dev,

1
Con i float, ==non significa che i numeri siano "identici al bit" (lo stesso numero può essere rappresentato con diversi schemi di bit, sebbene solo uno di essi sia una forma normalizzata). Inoltre, -0.0fe 0.0fsono rappresentati da diversi schemi di bit (il bit di segno è diverso), ma confrontano come uguali a ==(ma non con equals). La tua supposizione che ==sia un confronto bit a bit è, in generale, sbagliata.
Pavel Minaev,


5

Puoi usare Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

1
Sei sulla strada giusta. floatToIntBits () è la strada giusta da percorrere, ma sarebbe più semplice usare semplicemente la funzione equals () di Float. Vedi qui: stackoverflow.com/a/3668105/2066079 . Si può vedere che il valore predefinito equals () utilizza internamente floatToIntBits.
dberm22,

1
Sì se sono oggetti Float. Puoi usare sopra l'equazione per le primitive.
aamadmi,

4

Prima di tutto, sono float o float? Se uno di questi è Float, dovresti usare il metodo equals (). Inoltre, probabilmente è meglio usare il metodo statico Float.compare.


4

Quanto segue utilizza automaticamente la massima precisione:

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

Naturalmente, potresti scegliere più o meno di 5 ULP ("unità all'ultimo posto").

Se sei nella libreria di Apache Commons, la Precisionclasse ha compareTo()e equals()sia con epsilon che con ULP.


quando si cambia float in double, questo metodo non funziona come isDoubleEqual (0.1 + 0.2-0.3, 0.0) == false
hychou

Sembra che tu abbia bisogno di più come 10_000_000_000_000_000L come fattore per doublecoprire questo.
Michael Piefel,

3

potresti volerlo ==, ma 123.4444444444443! = 123.4444444444442



2

Due diversi calcoli che producono uguali numeri reali non producono necessariamente uguali numeri in virgola mobile. Le persone che usano == per confrontare i risultati dei calcoli di solito finiscono per essere sorprese da questo, quindi l'avvertimento aiuta a segnalare ciò che altrimenti potrebbe essere un bug sottile e difficile da riprodurre.


2

Hai a che fare con codice in outsourcing che userebbe float per cose denominate sectionID e currentSectionID? Solo curioso.

@Bill K: "La rappresentazione binaria di un float è piuttosto fastidiosa." Come mai? Come lo faresti meglio? Ci sono alcuni numeri che non possono essere rappresentati correttamente in nessuna base, perché non finiscono mai. Pi è un buon esempio. Puoi solo approssimarlo. Se hai una soluzione migliore, contatta Intel.


1

Come menzionato in altre risposte, i doppi possono avere piccole deviazioni. E potresti scrivere il tuo metodo per confrontarli usando una deviazione "accettabile". Però ...

Esiste una classe apache per confrontare i doppi: org.apache.commons.math3.util.Precision

Contiene alcune costanti interessanti: SAFE_MINe EPSILON, che sono le deviazioni massime possibili di semplici operazioni aritmetiche.

Fornisce inoltre i metodi necessari per confrontare, doppie uguali o rotonde. (usando ulps o deviazione assoluta)


1

In una riga di risposta posso dire che dovresti usare:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

Per apprendere di più sull'uso corretto degli operatori correlati, sto elaborando alcuni casi qui: in generale, ci sono tre modi per testare le stringhe in Java. Puoi usare ==, .equals () o Objects.equals ().

Come sono differenti? == verifica la qualità di riferimento nelle stringhe, il che significa scoprire se i due oggetti sono uguali. D'altra parte, .equals () verifica se le due stringhe hanno lo stesso valore logicamente. Infine, Objects.equals () verifica eventuali null nelle due stringhe, quindi determina se chiamare .equals ().

Operatore ideale da usare

Bene, questo è stato oggetto di numerosi dibattiti perché ciascuno dei tre operatori ha il proprio insieme unico di punti di forza e di debolezza. Esempio, == è spesso un'opzione preferita quando si confronta il riferimento a un oggetto, ma ci sono casi in cui può sembrare che paragonino anche i valori di stringa.

Tuttavia, ciò che ottieni è un valore di caduta perché Java crea l'illusione che stai confrontando i valori, ma in realtà non lo sei. Considera i due casi seguenti:

Caso 1:

String a="Test";
String b="Test";
if(a==b) ===> true

Caso 2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

Pertanto, è meglio utilizzare ciascun operatore durante il test dell'attributo specifico per cui è progettato. Ma in quasi tutti i casi, Objects.equals () è un operatore più universale, quindi l'esperienza degli sviluppatori web lo sceglie.

Qui puoi ottenere maggiori dettagli: http://fluentthemes.com/use-compare-strings-java/


-2

Il modo corretto sarebbe

java.lang.Float.compare(float1, float2)

7
Float.compare (float1, float2) restituisce un int, quindi non può essere utilizzato al posto di float1 == float2 nella condizione if. Inoltre, non risolve davvero il problema di fondo a cui si riferisce questo avviso: che se i float sono risultati del calcolo numerico, float1! = Float2 può verificarsi solo a causa di errori di arrotondamento.
quant_dev,

1
giusto, non puoi copiare incolla, devi prima controllare il documento.
Eric,

2
Quello che puoi fare invece di float1 == float2 è Float.compare (float1, float2) == 0.
deterb

29
Questo non ti compra nulla - ottieni ancoraFloat.compare(1.1 + 2.2, 3.3) != 0
Pavel Minaev il

-2

Un modo per ridurre l'errore di arrotondamento è utilizzare double anziché float. Ciò non risolverà il problema, ma ridurrà la quantità di errore nel programma e float non è quasi mai la scelta migliore. A PARER MIO.

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.