confrontando valori float / double usando l’operatore ==

Lo strumento di revisione del codice che uso si lamenta del seguente quando inizio a confrontare due valori float usando l’operatore di uguaglianza. Qual è il modo corretto e come farlo? Esiste una funzione di supporto (comuni) * che posso riutilizzare?

Descrizione

Imansible confrontare i valori in virgola mobile utilizzando l’operatore di uguale (==)

Spiegazione

Confrontando i valori in virgola mobile usando gli operatori di uguaglianza (==) o disuguaglianza (! =) Non sempre è accurato a causa degli errori di arrotondamento.

Raccomandazione

Confronta i due valori float per vedere se sono vicini di valore.

float a; float b; if(a==b) { .. } 

IBM ha una raccomandazione per confrontare due float, usando la divisione piuttosto che la sottrazione: ciò rende più facile la selezione di un epsilon che funzioni per tutti gli intervalli di input.

 if (abs(a/b - 1) < epsilon) 

Per quanto riguarda il valore di epsilon, vorrei usare 5.96e-08 come indicato in questa tabella di Wikipedia , o forse 2x quel valore.

Vuole che tu li paragoni con la precisione che ti serve. Ad esempio se richiedi che le prime 4 cifre decimali dei tuoi float siano uguali, allora useresti:

 if(-0.00001 <= ab && ab <= 0.00001) { .. } 

O:

 if(Math.abs(ab) < 0.00001){ ... } 

Dove aggiungi la precisione desiderata alla differenza dei due numeri e confrontala al doppio della precisione desiderata.

Qualunque cosa tu pensi sia più leggibile. Preferisco il primo io in quanto mostra chiaramente la precisione che stai permettendo su entrambi i lati.

a = 5.43421 b = 5.434205 supererà il confronto

 private static final float EPSILON = ; if (Math.abs(ab) < EPSILON) ... 

Poiché il punto in virgola mobile offre una precisione variabile ma incontrollabile (ovvero, non è ansible impostare la precisione se non quando si sceglie di utilizzare il double e il float ), è necessario scegliere la propria precisione fissa per i confronti.

Si noti che questo non è più un vero operatore di equivalenza, in quanto non è transitivo. Puoi facilmente ottenere a uguale b b uguale a c ma a non uguale a c .

Modifica: nota anche che se a è negativo e b è un numero positivo molto grande, la sottrazione può traboccare e il risultato sarà infinito negativo, ma il test funzionerà ancora, poiché il valore assoluto dell'infinito negativo è infinito positivo, che sarà essere più grande di EPSILON .

Usa commons-lang

 org.apache.commons.lang.math.NumberUtils#compare 

Anche commons-math (nella tua situazione soluzione più appropriata):

 http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double) 

Il tipo float è un valore approssimativo : c’è una parte esponenziale e una porzione valore con precisione finita.
Per esempio:

 System.out.println((0.6 / 0.2) == 3); // false 

Il rischio è che un piccolo errore di arrotondamento possa rendere false un confronto, quando matematicamente dovrebbe essere true .

La soluzione alternativa consiste nel confrontare i float consentendo a una differenza minore di essere ancora “uguale”:

 static float e = 0.00000000000001f; if (Math.abs(a - b) < e) 

Apache commons-math in soccorso: MathUtils. (Doppio x, doppio y, int maxUlps)

Restituisce vero se entrambi gli argomenti sono uguali o entro l'intervallo di errore consentito (incluso). Due numeri float sono considerati uguali se ci sono (maxUlps - 1) (o meno) numeri in virgola mobile tra di loro, cioè due numeri in virgola mobile adiacenti sono considerati uguali.

Ecco il codice effettivo dell’implementazione di Commons Math:

 private static final int SGN_MASK_FLOAT = 0x80000000; public static boolean equals(float x, float y, int maxUlps) { int xInt = Float.floatToIntBits(x); int yInt = Float.floatToIntBits(y); if (xInt < 0) xInt = SGN_MASK_FLOAT - xInt; if (yInt < 0) yInt = SGN_MASK_FLOAT - yInt; final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps; return isEqual && !Float.isNaN(x) && !Float.isNaN(y); } 

Questo ti dà il numero di float che possono essere rappresentati tra i tuoi due valori alla scala attuale, che dovrebbe funzionare meglio di un epsilon assoluto.

Ho preso una pugnalata a questo basato sul modo in cui java implementa == per i doppi. Converte prima il formato intero lungo IEEE 754 e poi esegue un confronto bit a bit. Double fornisce anche il doubleToLongBits () statico per ottenere la forma intera. Usando un po ‘di manipolazione puoi “arrotondare” la mantissa del doppio aggiungendo 1/2 (un bit) e troncando.

In linea con l’osservazione del supercat, la funzione prima prova un semplice confronto == e solo round se fallisce. Ecco cosa mi è venuto in mente con alcuni (auspicabilmente) commenti utili.

Ho fatto alcuni test limitati, ma non posso dire di aver provato tutti i casi limite. Inoltre, non ho testato le prestazioni. Non dovrebbe essere troppo cattivo.

Ho appena realizzato che questa è essenzialmente la stessa soluzione offerta da Dmitri. Forse un po ‘più conciso.

 static public boolean nearlyEqual(double lhs, double rhs){ // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers) // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL< 

La seguente modifica gestisce la modifica nel caso del segno in cui il valore è su entrambi i lati di 0.

 return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L); 

Innanzitutto, alcune cose da notare:

  • Il modo “Standard” per fare ciò è scegliere un epsilon costante, ma gli epsilon costanti non funzionano correttamente per tutti gli intervalli numerici.
  • Se si desidera utilizzare una costante epsilon sqrt(EPSILON) la radice quadrata di epsilon da float.h è generalmente considerata un buon valore. (questo deriva da un famigerato “libro arancione” il cui nome mi sfugge al momento).
  • La divisione in virgola mobile sarà lenta, quindi probabilmente vorrai evitarlo per i confronti anche se si comporta come scegliere un epsilon che è personalizzato per le grandezze dei numeri.

Cosa vuoi veramente fare? qualcosa come questo:
Confronta quanti numeri in virgola mobile rappresentabili differiscono per i valori.

Questo codice proviene da questo articolo davvero eccezionale di Bruce Dawson. L’articolo è stato da allora aggiornato qui . La principale differenza è che il vecchio articolo interrompe la regola del rigo-aliasing. (lanciare puntatori mobili a puntatore int, dereferenziazione, scrittura, richiama). Mentre il purista di C / C ++ metterà subito in evidenza il difetto, in pratica funziona, e considero il codice più leggibile. Tuttavia, il nuovo articolo utilizza i sindacati e C / C ++ ottiene la sua dignità. Per brevità do il codice che rompe il rigoroso aliasing sotto.

 // Usable AlmostEqual function bool AlmostEqual2sComplement(float A, float B, int maxUlps) { // Make sure maxUlps is non-negative and small enough that the // default NAN won't compare as equal to anything. assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); int aInt = *(int*)&A; // Make aInt lexicographically ordered as a twos-complement int if (aInt < 0) aInt = 0x80000000 - aInt; // Make bInt lexicographically ordered as a twos-complement int int bInt = *(int*)&B; if (bInt < 0) bInt = 0x80000000 - bInt; int intDiff = abs(aInt - bInt); if (intDiff <= maxUlps) return true; return false; } 

L'idea di base nel codice qui sopra è di notare che, dato il formato in virgola mobile IEEE 754, {sign-bit, biased-exponent, mantissa} , i numeri sono ordinati lessicograficamente se interpretati come valori di grandezza firmati. Questo è il bit del segno che diventa il bit del segno, e l'esponente supera sempre completamente la mantissa nella definizione della grandezza del float e perché viene prima nel determinare la grandezza del numero interpretato come un int.

Quindi, interpretiamo la rappresentazione di bit del numero in virgola mobile come int di magnitudine firmata. Quindi convertiamo gli input di grandezza firmati in integratori di due di due sottraendoli da 0x80000000 se il numero è negativo. Quindi confrontiamo i due valori come se avessimo tutti gli interi di complemento firmati e vedendo quanti valori differiscono. Se questo importo è inferiore alla soglia scelta per quanti float rappresentabili i valori possono essere diversi e comunque essere considerati uguali, allora dici che sono "uguali". Si noti che questo metodo consente correttamente che i numeri "uguali" differiscano per valori maggiori per i galleggianti di maggiore ampiezza e per valori più piccoli per i galleggianti di minore ampiezza.

Ci sono molti casi in cui si vuole considerare due numeri in virgola mobile come uguali solo se sono assolutamente equivalenti, e un confronto “delta” sarebbe sbagliato. Per esempio, se f è una funzione pura), e si sa che q = f (x) ey === x, allora si dovrebbe sapere che q = f (y) senza doverlo calcolare. Sfortunatamente il == ha due difetti in questo senso.

  • Se un valore è uno zero positivo e l’altro è uno zero negativo, essi verranno confrontati come uguali anche se non sono necessariamente equivalenti. Ad esempio se f (d) = 1 / d, a = 0 eb = -1 * a, quindi a == b ma f (a)! = F (b).

  • Se uno dei due valori è un NaN, il confronto restituirà sempre false anche se un valore è stato assegnato direttamente dall’altro.

Anche se ci sono molti casi in cui il controllo dei numeri in virgola mobile per l’equivalenza esatta è giusto e corretto, non sono sicuro di casi in cui il comportamento effettivo di == dovrebbe essere considerato preferibile. Probabilmente, tutti i test per l’equivalenza dovrebbero essere eseguiti tramite una funzione che verifica effettivamente l’equivalenza (ad esempio confrontando le forms bit a bit).