La classe BigDecimal
consente di utilizzare numeri a precisione decimale "arbitraria" (vedremo poi i limiti di questa definizione) e di eseguire calcoli aritmetici su di essi. Il suo utilizzo tuttavia non è immediato ed è facile commettere errori.
In questo articolo si cercherà di fare un po' di chiarezza sull'argomento, nella speranza di vedere diminuire la quantità di bug nei programmi che usano BigInteger
e BigDecimal
.
Cos'è
Il package java.math
contiene due classi dedicate alla cosiddetta "aritmetica a precisione arbitraria": BigInteger
e BigDecimal
. Entrambe servono per gestire numeri la cui grandezza o precisione supera i limiti imposti dai tipi standard di Java: int
, long
, double
, float
, ecc.
java.math.BigInteger
Questa classe serve per rappresentare numeri interi immutabili di grandezza arbitraria; il massimo numero di cifre memorizzabili è Integer.MAX_VALUE.
java.math.BigDecimal
Questa classe serve per rappresentare numeri decimali immutabili di precisione arbitraria, ed è composta da due parti distinte:
- valore unscaled (non scalato): è un valore di tipo
BigInteger
che rappresenta il numero privato della virgola; - scale (scala): è un numero di tipo
int
che indica quante posizioni decimali ci sono dopo la virgola.
Il valore di un numero BigDecimal
è quindi: unscaled / 10^<sup>scale</sup>
A cosa serve
L'utilizzo di numeri in virgola mobile presenta dei brutti inconvenienti che li rendono non adatti per alcuni tipi di calcoli, in primis quelli finanziari (ma qualcuno ancora lo fa...capirete la mia rabbia allora...). Da sempre infatti i programmi gestionali di banche e assicurazioni utilizzano i cosiddetti numeri decimali a virgola fissa, i quali altro non sono che numeri interi sui quali si applica un numero fisso di posizioni decimali (anche molto elevato). Questo approccio consente di tenere sotto controllo alcune delle anomalie prodotte dai calcoli con numeri in virgola mobile (IEEE 754), ma a prezzo di una gestione più difficile.
java.math.BigDecimal
serve proprio a gestire questo tipo di calcoli e si spinge oltre, fornendo molti metodi di arrotondamento e garantendo una precisione altissima.
Come si usa
Una caratteristica fondamentale di BigInteger
e BigDecimal
è che le loro istanze sono immutabili: tutte le operazioni restituiscono un nuovo oggetto contenente il risultato dell'operazione. Anche i metodi come setScale()
, movePointLeft() / movePointRight()
o negate()
, che dal nome sembrerebbero in grado di modificare in qualche modo l'istanza, restituiscono un nuovo oggetto con il valore mutato.
Ecco un esempio:
package it.megadix.math;
import java.math.BigDecimal;
public class BigDecimalTest1 {
public static void main(String[] args) {
BigDecimal n1 = new BigDecimal("1000.123482248908085303458975309348");
System.out.println("n1 = " + n1);
BigDecimal n2 = new BigDecimal("2000.122837345398340801010291390210252");
System.out.println("n2 = " + n2);
BigDecimal resultAdd = n1.add(n2);
System.out.println("n1 + n2 = " + resultAdd);
BigDecimal resultMultiply = n1.multiply(n2);
System.out.println("n1 * n2 = " + resultMultiply);
BigDecimal resultDivide = n1.divide(n2, BigDecimal.ROUND_HALF_UP);
System.out.println("n1 / n2 = " + resultDivide);
}
}
Output del programma:
n1 = 1000.123482248908085303458975309348
n2 = 2000.122837345398340801010291390210252
n1 + n2 = 3000.246319594306426104469266699558252
n1 * n2 = 2000369.817011446171094293893027628836590866233492522879727390461035696
n1 / n2 = 0.500031029882290273171404981367
Alcune cose da notare:
- utilizzo del costruttore
BigDecimal(String)
. Questo costruttore accetta una notazione simile a quella inglese per i numeri decimali: segno, cifre prima della virgola, frazione ed esponente. Per i dettagli si rimanda alla documentazione ufficiale; - il risultato di ogni operazione è una nuova istanza di
BigDecimal
; - il metodo
BigDecimal.toString()
è compatibile con il costruttoreBigDecimal(String)
.
Formattazione
La formattazione di numeri decimali è una faccenda molto complicata, entrano infatti in gioco fattori come:
- lingua e località: ad esempio in inglese si usano le virgole per separare le miglialia e i punti per separare i decimali, in italiano e in francese invece è tutto il contrario;
- alfabeto: i caratteri utilizzati per rappresentare i numeri possono variare (occidentale, ebraico, arabo, hindi...);
- zeri iniziali e/o finali: alcuni esempi sono ".1", "0.1", ".10", "0.1000", ecc.;
- segno "+" e "-";
- esponenti e notazione scientifica;
- ...non è abbastanza?
Di seguito un semplice esempio di formattazione che imposta la lingua e il numero di cifre decimali:
package it.megadix.math;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
public class BigDecimalTestFormat {
public static void main(String[] args) {
BigDecimal n = new BigDecimal("1000" +
"." +
"00000000000000000000000000000000000000000000000000" +
"1" +
"000");
NumberFormat fmt_EN = NumberFormat.getNumberInstance(Locale.ENGLISH);
fmt_EN.setMaximumFractionDigits(1000);
NumberFormat fmt_IT = NumberFormat.getNumberInstance(Locale.ITALIAN);
fmt_IT.setMaximumFractionDigits(1000);
System.out.println(fmt_EN.format(n));
System.out.println(fmt_IT.format(n));
}
}
Output del programma:
1,000.000000000000000000000000000000000000000000000000001
1.000,000000000000000000000000000000000000000000000000001
Da notare:
- la stringa utilizzata per creare il numero da formattare è stata spezzata su più righe per evidenzarne le varie parti;
- la classe di formattazione è
java.text.NumberFormat
; - è stato utlilizzato il costruttore di NumberFormat che accetta come parametro un'istanza di
java.util.Locale
.
Sviluppi
Queste informazioni sono il minimo indispensabile per utilizzare la classe BigDecimal
; per approfondimenti si consiglia di leggere attentamente la documentazione ufficiale della Sun e i molti tutorial disponibili su Internet.