Devo calcolare alcune variabili in virgola mobile e il mio collega mi suggerisce di utilizzare BigDecimal
invece che double
dal momento che sarà più preciso. Ma voglio sapere di cosa si tratta e come trarne il massimo BigDecimal
?
Devo calcolare alcune variabili in virgola mobile e il mio collega mi suggerisce di utilizzare BigDecimal
invece che double
dal momento che sarà più preciso. Ma voglio sapere di cosa si tratta e come trarne il massimo BigDecimal
?
Risposte:
A BigDecimal
è un modo esatto di rappresentare i numeri. A Double
ha una certa precisione. Lavorare con doppi di varie dimensioni (dire d1=1000.0
e d2=0.001
) potrebbe comportare la 0.001
caduta totale quando si sommano poiché la differenza di grandezza è così grande. Con BigDecimal
questo non accadrebbe.
Lo svantaggio di BigDecimal
è che è più lento, ed è un po 'più difficile programmare gli algoritmi in quel modo (a causa +
-
*
e /
non sovraccarico).
Se hai a che fare con il denaro, o la precisione è un must, usa BigDecimal
. Altrimenti Doubles
tende ad essere abbastanza buono.
Consiglio di leggere il javadoc di BigDecimal
come spiegano le cose meglio di me qui :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
", un Double avrebbe più "precisione" (più cifre).
Il mio inglese non è buono, quindi scriverò un semplice esempio qui.
double a = 0.02;
double b = 0.03;
double c = b - a;
System.out.println(c);
BigDecimal _a = new BigDecimal("0.02");
BigDecimal _b = new BigDecimal("0.03");
BigDecimal _c = _b.subtract(_a);
System.out.println(_c);
Uscita del programma:
0.009999999999999998
0.01
Qualcuno vuole ancora usare il doppio? ;)
System.out.println(0.003f - 0.002f);
BigDecimal è esatto:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Ci sono due differenze principali rispetto al doppio:
Il motivo per cui dovresti usare BigDecimal per i calcoli monetari non è che può rappresentare qualsiasi numero, ma che può rappresentare tutti i numeri che possono essere rappresentati in nozioni decimali e che includono praticamente tutti i numeri nel mondo monetario (non trasferisci mai 1/3 $ a qualcuno).
Se si annota un valore frazionario come 1 / 7
il valore decimale che si ottiene
1/7 = 0.142857142857142857142857142857142857142857...
con una sequenza infinita di 142857
. Dal momento che è possibile scrivere solo un numero finito di cifre, si verificherà inevitabilmente un errore di arrotondamento (o troncamento).
I numeri come 1/10
o 1/100
espressi come numeri binari con una parte frazionaria hanno anche un numero infinito di cifre dopo il punto decimale:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
memorizzare i valori come binari e quindi potrebbe introdurre un errore solo convertendo un numero decimale in un numero binario, senza nemmeno fare alcuna aritmetica.
I numeri decimali (come BigDecimal
), d'altra parte, memorizzano ogni cifra decimale così com'è. Ciò significa che un tipo decimale non è più preciso di un punto mobile binario o di un punto fisso in senso generale (cioè non può memorizzare1/7
senza perdita di precisione), ma è più preciso per i numeri che hanno un numero finito di cifre decimali come è spesso il caso dei calcoli del denaro.
Java BigDecimal
ha l'ulteriore vantaggio di poter avere un numero arbitrario (ma finito) di cifre su entrambi i lati del punto decimale, limitato solo dalla memoria disponibile.
BigDecimal è la libreria numerica di precisione arbitraria di Oracle. BigDecimal fa parte del linguaggio Java ed è utile per una varietà di applicazioni che vanno dal finanziario al scientifico (ecco dove sono una specie di am).
Non c'è niente di sbagliato nell'usare i doppi per determinati calcoli. Supponiamo, tuttavia, che tu voglia calcolare Math.Pi * Math.Pi / 6, ovvero il valore della funzione Ziem di Riemann per un vero argomento a due (un progetto su cui sto attualmente lavorando). La divisione in virgola mobile presenta un doloroso problema di errore di arrotondamento.
BigDecimal, d'altra parte, include molte opzioni per calcolare espressioni con precisione arbitraria. I metodi di aggiunta, moltiplicazione e divisione come descritto nella documentazione Oracle di seguito "prendono il posto" di +, * e / in BigDecimal Java World:
http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
Il metodo compareTo è particolarmente utile in while e per i loop.
Fai attenzione, tuttavia, nel tuo uso di costruttori per BigDecimal. Il costruttore di stringhe è molto utile in molti casi. Ad esempio, il codice
BigDecimal onethird = new BigDecimal ("0.33333333333");
utilizza una rappresentazione di stringa di 1/3 per rappresentare quel numero che si ripete all'infinito con un determinato grado di precisione. L'errore di arrotondamento è molto probabilmente in un punto così profondo all'interno della JVM che gli errori di arrotondamento non disturberanno la maggior parte dei calcoli pratici. Tuttavia, per esperienza personale, ho assistito a un riepilogo generale. Il metodo setScale è importante sotto questo aspetto, come si può vedere dalla documentazione di Oracle.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
Se hai a che fare con il calcolo, ci sono leggi su come dovresti calcolare e quale precisione dovresti usare. Se fallisci, farai qualcosa di illegale. L'unica vera ragione è che la rappresentazione in bit dei casi decimali non è precisa. Come Basil ha semplicemente detto, un esempio è la migliore spiegazione. Solo per completare il suo esempio, ecco cosa succede:
static void theDoubleProblem1() {
double d1 = 0.3;
double d2 = 0.2;
System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));
float f1 = 0.3f;
float f2 = 0.2f;
System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));
BigDecimal bd1 = new BigDecimal("0.3");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}
Produzione:
Double: 0,3 - 0,2 = 0.09999999999999998
Float: 0,3 - 0,2 = 0.10000001
BigDec: 0,3 - 0,2 = 0.1
Inoltre abbiamo che:
static void theDoubleProblem2() {
double d1 = 10;
double d2 = 3;
System.out.println("Double:\t 10 / 3 = " + (d1 / d2));
float f1 = 10f;
float f2 = 3f;
System.out.println("Float:\t 10 / 3 = " + (f1 / f2));
// Exception!
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}
Ci dà l'output:
Double: 10 / 3 = 3.3333333333333335
Float: 10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion
Ma:
static void theDoubleProblem2() {
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}
Ha l'output:
BigDec: 10 / 3 = 3.3333