Java BigDecimal
Large numbers in Java
Sometimes programmers have to work with extremely large numbers. Since standard primitive types cannot store them, there are two special classes for this purpose: BigInteger for integer numbers and BigDecimal for floating-point numbers.
The size of numbers isn't limited by anything but the physical memory of your computer. In the case of BigDecimal
class, you can have as many digits after the decimal point as you want, which is important for accurate calculations. There are applications where the accuracy of computation is crucial, such as aircraft or medical software, or those used for storing and processing financial data.
Creating objects of BigDecimal
To create an instance of BigDecimal
, the first thing you need to do is import this class from java.math
package using the following statement:
import java.math.BigDecimal;
The recommended way is to create an instance of BigDecimal
from the String
object and specify the desired number in double quotes.
BigDecimal firstBigDecimal = new BigDecimal("10000000000000.5897654329");
As you can see, the syntax is consistent and pretty simple.
It is worth mentioning thatBigDecimal
has several useful constants, just like BigInteger
:
BigDecimal zero = BigDecimal.ZERO; // 0
BigDecimal one = BigDecimal.ONE; // 1
BigDecimal ten = BigDecimal.TEN; // 10
Arithmetic operations
It is extremely important to keep in mind that BigDecimal
is an immutable class. Immutability implies that you cannot change an existing instance of BigDecimal
. If you try to modify an existing object, a new object is created.
Remember: BigDecimal
numbers are immutable.
You might remember that in the case of double
and float
, there are a few potential problems with the floating-point representation. For instance, the result of adding 0.2
to 0.1
won't be 0.3
, which affects the accuracy of further calculations:
System.out.println(0.1 + 0.2); // 0.30000000000000004
BigDecimal
has no such problem: the results of all operations will be absolutely correct.
In the code snippet below, you can see some examples of binary methods with BigDecimal
:
BigDecimal first = new BigDecimal("0.2");
BigDecimal second = new BigDecimal("0.1");
BigDecimal addition = first.add(second); // 0.3
BigDecimal subtraction = first.subtract(second); // 0.1
BigDecimal multiplication = first.multiply(second); // 0.02
BigDecimal division = first.divide(second); // 2
BigDecimal remainder = first.remainder(second); // 0.0
Now, let's take a look at some unary methods:
BigDecimal first = new BigDecimal("0.2");
// absolute value
BigDecimal module = first.abs(); // 0.2
// raise to the power
BigDecimal power = first.pow(3); // 0.008
Rounding control
When we need to tweak the accuracy by specifying the number of digits after the point, setScale()
method comes to the rescue. It allows us to adjust the precision of large fractional numbers:
bigDecimal.setScale(newScale, RoundingMode);
The first parameter is newScale
. It sets the number of digits after the decimal point. Here is how you may figure out the scale of your number:
BigDecimal fractionalNumber = new BigDecimal("123.4567");
System.out.println(fractionalNumber.scale()); // 4
The second parameter — roundingMode
— allows us to specify the way your number will be rounded. To use it, you need to perform the import:
import java.math.RoundingMode;
You can find the list of all the possible BigDecimal
class rounding modes along with their brief descriptions in the table below:
This might seem a little abstract, so let's look at some examples that will help us sort things out.
Rounding mode examples
As you now know, you can adjust the accuracy of your large numbers and choose the rules by which they will be rounded. The following code snippet shows some examples of using BigDecimal
rounding with different rounding modes:
BigDecimal bigDecimal = new BigDecimal("100.5649");
System.out.println(bigDecimal.setScale(3, RoundingMode.CEILING)); // 100.565
bigDecimal = new BigDecimal("0.55");
System.out.println(bigDecimal.setScale(1, RoundingMode.HALF_DOWN)); // 0.5
System.out.println(bigDecimal.setScale(3, RoundingMode.UNNECESSARY)); // 0.550
Remember that BigDecimal
numbers are immutable, so it is not enough to simply apply setScale()
in order for your number to retain the new value after rounding. You need to assign:
BigDecimal bigDecimal = new BigDecimal("999999999999999999.99999999999999");
bigDecimal.setScale(3, RoundingMode.HALF_UP);
System.out.println(bigDecimal); // 999999999999999999.99999999999999
bigDecimal = bigDecimal.setScale(3, RoundingMode.HALF_UP);
System.out.println(bigDecimal); // 1000000000000000000.000
You can easily compare the difference in behavior depending on rounding modes using the table below. These are the examples of Different Rounding Modes, precision is set to 0.
Note that UNNECESSARY
will add insignificant zeros to the number if you specify too many digits in setScale()
. Then again, if you specify too few digits, an error will occur.
Rounding in arithmetic operations
Finally, let's discuss something a bit more advanced: at this point, you should have enough background knowledge for that.
If the result of a division has a non-terminating decimal expansion, it cannot be represented as a BigDecimal
and ArithmeticException
happens:
BigDecimal dividend = new BigDecimal("1");
BigDecimal divisor = new BigDecimal("3");
// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
BigDecimal quotient = dividend.divide(divisor);
To avoid it, you need to determine the accuracy of the division result.
BigDecimal dividend = new BigDecimal("1");
BigDecimal divisor = new BigDecimal("3");
BigDecimal quotient = dividend.divide(divisor, 2, RoundingMode.HALF_EVEN); // 0.33
An exact scale is used if the result can be represented by a finite decimal expansion:
BigDecimal first = new BigDecimal("20.002");
BigDecimal second = new BigDecimal("10");
BigDecimal division = first.divide(second); // 2.0002
Addition, subtraction, and multiplication have no such peculiarities. Even though precision also matters there and is used in arithmetic operations, it's quite intuitive:
BigDecimal first = new BigDecimal("7.7777");
BigDecimal second = new BigDecimal("3.3");
BigDecimal addition = first.add(second); // 11.0777; The result scale is 4 (max of the scales)
BigDecimal subtraction = first.subtract(second); // 4.4777; The result scale is 4 (max of the scales)
BigDecimal multiplication = first.multiply(second); // 25.66641; The result scale is 5 (sum of the scales)
Here is how we can describe the accuracy of the output:
- Addition: the maximum scale of the addends;
- Subtraction: the maximum scale of the minuend and subtrahend;
- Multiplication: the sum of the multiplier and multiplicand scales;
- Division: the resulting scale, or set manually in the case of the non-terminating decimal expansion.
Conclusion
The BigDecimal
class is useful for storing large fractional numbers. Standard arithmetic operations are also available for BigDecimal
numbers. You can manage the rounding behavior of the objects of this class with setScale()
indicating the desired number of digits as the first parameter and the rounding mode as the second parameter.