Doppio vs BigDecimal?


Risposte:


446

A BigDecimalè un modo esatto di rappresentare i numeri. A Doubleha una certa precisione. Lavorare con doppi di varie dimensioni (dire d1=1000.0e d2=0.001) potrebbe comportare la 0.001caduta totale quando si sommano poiché la differenza di grandezza è così grande. Con BigDecimalquesto 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 Doublestende ad essere abbastanza buono.

Consiglio di leggere il javadoc di BigDecimalcome spiegano le cose meglio di me qui :)


Sì, sto calcolando il prezzo per lo stock quindi credo che BigDecimal sia utile in questo caso.
Truong Ha,

5
@Truong Ha: quando lavori con i prezzi vuoi usare BigDecimal. E se li memorizzi nel database vuoi qualcosa di simile.
extraneon

98
Dire che "BigDecimal è un modo esatto di rappresentare i numeri" è fuorviante. 1/3 e 1/7 non possono essere espressi esattamente in un sistema numerico in base 10 (BigDecimal) o in un sistema numerico in base 2 (float o double). 1/3 potrebbe essere espresso esattamente nella base 3, base 6, base 9, base 12, ecc. E 1/7 potrebbe essere espresso esattamente nella base 7, base 14, base 21, ecc. I vantaggi di BigDecimal sono la precisione arbitraria e che gli umani sono abituati agli errori di arrotondamento che si verificano nella base 10.
procrastinate_later

3
L' if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
aspetto positivo

@extraneon Penso che intendi dire "se la precisione è un must, usa BigDecimal", un Double avrebbe più "precisione" (più cifre).
jspinella,

164

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? ;)


11
@eldjon Non è vero, guarda questo esempio: BigDecimal two = new BigDecimal ("2"); BigDecimal otto = nuovo BigDecimal ("8"); System.out.println (two.divide (otto)); Questo stampa 0,25.
Ludvig W

4
raddoppia perevr: D
vach

Tuttavia se si utilizza un float invece si ottiene la stessa precisione di BigDecimal in quel caso ma prestazioni
decisamente

3
@EliuX Float potrebbe funzionare con 0,03-0,02, ma altri valori sono ancora imprecisi: System.out.println(0.003f - 0.002f);BigDecimal è esatto:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin

50

Ci sono due differenze principali rispetto al doppio:

  • Precisione arbitraria, analogamente a BigInteger possono contenere numero di precisione e dimensioni arbitrarie
  • Base 10 anziché Base 2, un BigDecimal ha una scala n * 10 ^ dove n è un intero con segno di grandi dimensioni arbitrario e la scala può essere considerata come il numero di cifre per spostare il punto decimale a sinistra oa destra

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).


2
Questa risposta spiega davvero la differenza e il motivo dell'utilizzo di BigDecimal rispetto al doppio. Le preoccupazioni relative alle prestazioni sono secondarie.
Vortice

Questo non è vero al 100%. Hai scritto che un BigDecimal è "n * 10 ^ scala". Java lo fa solo per numeri negativi. Quindi sarebbe corretto: "unscaledValue × 10 ^ -scale". Per numeri positivi, BigDecimal è costituito da un "valore intero non scalato di precisione arbitraria e una scala di numeri interi a 32 bit" mentre la scala è il numero di cifre a destra del punto decimale.
la mano di NOD

25

Se si annota un valore frazionario come 1 / 7il 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/10o 1/100espressi 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 BigDecimalha 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.


7

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.


BigDecimal fa parte della libreria numerica di precisione arbitraria di Java . "In-house" è piuttosto insignificante in questo contesto, specialmente perché è stato scritto da IBM.
Marchese di Lorne,

@EJP: ho esaminato la classe BigDecimal e ho appreso che solo una parte è stata scritta da IBM. Commento sul copyright di seguito: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK

7

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 
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.