Java doppio confronto epsilon

Ho scritto una class che prova per l’uguaglianza, meno di, e maggiore rispetto a due doppi in Java. Il mio caso generale è quello di confrontare il prezzo che può avere un’accuratezza di mezzo centesimo. 59.005 rispetto a 59.395. L’epsilon che ho scelto è adeguato per questi casi?

private final static double EPSILON = 0.00001; /** * Returns true if two doubles are considered equal. Tests if the absolute * difference between two doubles has a difference less then .00001. This * should be fine when comparing prices, because prices have a precision of * .001. * * @param a double to compare. * @param b double to compare. * @return true true if two doubles are considered equal. */ public static boolean equals(double a, double b){ return a == b ? true : Math.abs(a - b) < EPSILON; } /** * Returns true if two doubles are considered equal. Tests if the absolute * difference between the two doubles has a difference less then a given * double (epsilon). Determining the given epsilon is highly dependant on the * precision of the doubles that are being compared. * * @param a double to compare. * @param b double to compare * @param epsilon double which is compared to the absolute difference of two * doubles to determine if they are equal. * @return true if a is considered equal to b. */ public static boolean equals(double a, double b, double epsilon){ return a == b ? true : Math.abs(a - b)  epsilon; } /** * Returns true if the first double is considered less than the second * double. Test if the difference of second minus first is greater then * .00001. This should be fine when comparing prices, because prices have a * precision of .001. * * @param a first double * @param b second double * @return true if the first double is considered less than the second * double */ public static boolean lessThan(double a, double b){ return lessThan(a, b, EPSILON); } /** * Returns true if the first double is considered less than the second * double. Test if the difference of second minus first is greater then * a given double (epsilon). Determining the given epsilon is highly * dependant on the precision of the doubles that are being compared. * * @param a first double * @param b second double * @return true if the first double is considered less than the second * double */ public static boolean lessThan(double a, double b, double epsilon){ return b - a > epsilon; } 

NON si usa il doppio per rappresentare il denaro. Non mai. Utilizzare invece java.math.BigDecimal .

Quindi puoi specificare esattamente come arrotondare (che a volte è dettato dalla legge nelle applicazioni finanziarie!) E non devi fare stupidi hack come questa cosa di epsilon.

Seriamente, usare i tipi a virgola mobile per rappresentare il denaro è estremamente poco professionale.

Sì. I doppi Java manterranno la loro precisione meglio del tuo epsilon dato di 0,00001.

Qualsiasi errore di arrotondamento che si verifica a causa della memorizzazione di valori in virgola mobile si verificherà inferiore a 0,00001. Uso regolarmente 1E-6 o 0,000001 per un doppio epsilon in Java senza problemi.

Su una nota correlata, mi piace il formato di epsilon = 1E-5; perché ritengo sia più leggibile (1E-5 in Java = 1 x 10 ^ -5). 1E-6 è facile da distinguere da 1E-5 durante la lettura del codice mentre 0.00001 e 0.000001 appaiono così simili quando si guarda il codice penso che abbiano lo stesso valore.

Whoa whoa whoa. Esiste un motivo specifico per cui utilizzi il floating-point per la valuta, oppure le cose andrebbero meglio con un formato numerico a precisione fissa e arbitraria ? Non ho idea di quale sia il problema specifico che stai cercando di risolvere, ma dovresti pensare se o meno mezzo centesimo è davvero qualcosa con cui vuoi lavorare, o se è solo un artefatto di usare un formato numerico impreciso.

Se hai a che fare con i soldi, ti suggerisco di controllare il modello di progettazione di Money (originariamente dal libro di Martin Fowler sul design di architettura aziendale ).

Suggerisco di leggere questo link per la motivazione: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

Se è ansible utilizzare BigDecimal, quindi utilizzarlo, altrimenti:

 /** *@param precision number of decimal digits */ public static boolean areEqualDouble(double a, double b, int precision) { return Math.abs(a - b) <= Math.pow(10, -precision); } 

Mentre sono d’accordo con l’idea che il doppio è un male per il denaro, l’idea di confrontare i doppi ha ancora interesse. In particolare l’uso suggerito di epsilon è adatto solo ai numeri in un intervallo specifico. Ecco un uso più generale di un epsilon, relativo al rapporto tra i due numeri (il test per 0 è omesso):

 boolean equal(double d1, double d2) { double d = d1 / d2; return (Math.abs(d - 1.0) < 0.001); } 

I numeri in virgola mobile hanno solo tante cifre significative, ma possono andare molto più in alto. Se la tua app manterrà sempre numeri grandi, noterai che il valore di epsilon dovrebbe essere diverso.

0,001 + 0,001 = 0,002 MA 12,345,678,900,000,000,000,000 + 1 = 12,345,678,900,000,000,000,000 se si utilizza il floating point e il double. Non è una buona rappresentazione del denaro, a meno che non sia dannatamente sicuro che non gestirai mai più di un milione di dollari in questo sistema.

Centesimi? Se stai calcolando i valori monetari, non dovresti usare i valori float. Il denaro è in realtà valori numerabili. I centesimi, i penny ecc. Possono essere considerati le due (o qualsiasi altra) cifra meno significativa di un intero. È ansible memorizzare e calcolare i valori monetari come numeri interi e dividerli per 100 (ad esempio, posizionare il punto o la virgola due prima delle ultime due cifre). L’uso di float può portare a strani errori di arrotondamento …

Ad ogni modo, se il tuo epsilon dovrebbe definire la precisione, sembra un po ‘troppo piccolo (troppo accurato) …

Come altri commentatori hanno correttamente notato, non si dovrebbe mai usare l’aritmetica a virgola mobile quando sono richiesti valori esatti, come per i valori monetari. Il motivo principale è in effetti il ​​comportamento di arrotondamento inerente ai punti fluttuanti, ma non dimentichiamo che affrontare i punti fluttuanti significa anche dover gestire valori infiniti e NaN.

A dimostrazione che il tuo approccio semplicemente non funziona, ecco un semplice codice di test. Semplicemente aggiungo EPSILON a 10.0 e osservo se il risultato è uguale a 10.0 – che non dovrebbe essere, poiché la differenza non è chiaramente inferiore a EPSILON :

  double a = 10.0; double b = 10.0 + EPSILON; if (!equals(a, b)) { System.out.println("OK: " + a + " != " + b); } else { System.out.println("ERROR: " + a + " == " + b); } 

Sorpresa:

  ERROR: 10.0 == 10.00001 

Gli errori si verificano a causa della perdita di bit significativi sulla sottrazione se due valori in virgola mobile hanno esponenti diversi.

Se pensi di applicare un approccio più avanzato alla “differenza relativa” come suggerito da altri commentatori, dovresti leggere l’eccellente articolo di Bruce Dawson che confronta i numeri in virgola mobile, edizione 2012 , che mostra che questo approccio ha carenze simili e che in realtà non vi è alcun errore. confronto in virgola mobile approssimativo e sicuro che funziona per tutte le gamme di numeri in virgola mobile.

Per rendere le cose brevi: astenersi da double s per i valori monetari e utilizzare rappresentazioni di numeri esatti come BigDecimal . Per motivi di efficienza, è ansible utilizzare anche i longs interpretati come “millis” (decimi di centesimi), a condizione che si evitino in modo affidabile over e underflow. Questo produce un valore massimo rappresentabile di 9'223'372'036'854'775.807 , che dovrebbe essere sufficiente per la maggior parte delle applicazioni del mondo reale.