java.math.BigDecimal
class represents “Immutable, arbitrary-precision signed decimal numbers” (with some limits), and has methods to operate on those numbers. Its use although is not so immediate and it is easy to do errors.
In this article I'll try to put some light on this topic, in the hope of seeing a lowering in the amount of bugs related to code using BigInteger
and BigDecimal
.
What is it
The package java.math
contains two classes targeted at "arbitrary precision math": BigInteger
and BigDecimal
. Both handle numbers whose magnitude overcome the limits imposed by standard Java types such as int
, long
, double
, float
, ecc.
java.math.BigInteger
This class handles immutable, integer numbers of arbitrary length; the maximum amount of usable digits is Integer.MAX_VALUE.
java.math.BigDecimal
This class handles immutable, decimal numbers of arbitrary length; as above, the amount of usable digits is limited by the available memory. It is composed of two parts:
- unscaled value: it is a
BigInteger
object holding the number representation withouts the decimal symbol. - scale: an
int
number indicating how many decimal digits we have.
So the value of a BigDecimal
number is: unscaled / 10^<sup>scale</sup>
What is its purpose?
Floating-point math has some serious drawbacks that makes it impossible to use them in some kind of calculations, especially the financial ones (although some still uses them...you understand my rage now...). Countless bank software are using fixed point math to do their calculations: they are simply integer numbers on which is applied a fixed amount of decimal places (even very high). This approach can help mitigate some of the anomalies of floating-point math (IEEE 754), but at the price of a more difficult handling.
java.math.BigDecimal
aim to solve these kind of problems, but they even go further with many useful rounding methods and an impressive high precision.
How is it used
A fundamental characteristic of BigInteger
and BigDecimal
is that their instances are immutable: all their methods return a new object holding the result of the operation. Even methods like setScale()
, movePointLeft() / movePointRight()
or negate()
, that sounds like they're modifying the object, return a new instance with the result.
Some examples:
package org.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:
n1 = 1000.123482248908085303458975309348
n2 = 2000.122837345398340801010291390210252
n1 + n2 = 3000.246319594306426104469266699558252
n1 * n2 = 2000369.817011446171094293893027628836590866233492522879727390461035696
n1 / n2 = 0.500031029882290273171404981367
Things to note:
- use of the
BigDecimal(String)
constructor. This constructoraccepts an english-like notation: sign, integer part, fraction and exponent. For the detail visit the official documentation (Javadoc); - the result of any operation is a new instance of
BigDecimal
; - the
BigDecimal.toString()
method is two-way compatible withBigDecimal(String)
constructor.
Formatting
Decimal numbers formatting is a complicated matter:
- language and location: for example in GB or USA commas are used to separate thousands and dots to separate decimals, but in Italy and France it is the opposite;
- alphabet: characters used to represent numbers can vary, think about western, ebraic, arab, hindi...);
- leading/trailing zeroes: ".1", "0.1", ".10", "0.1000", ...
- presence or not of sign symbols (+/-);
- scientific notation;
- ...isn't it enough?
Here is a simple example of formatting that sets language and number of decimal places:
package org.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:
1,000.000000000000000000000000000000000000000000000000001
1.000,000000000000000000000000000000000000000000000000001
Things to note
- the format string has been split into multiple rows for better readability;
- we leveraged the methods of
java.text.NumberFormat
for formatting; - we used the
NumberFormat(java.util.Locale)
constructor.
Developments
These article has given you the basics to operate on BigDecimal
; anyway, you are strongly encouraged to read the official Java documentation.