1. Overview
In this tutorial, we will demonstrate BigDecimal and the BigInteger classes.
We’ll describe the two data types, their characteristics, and their usage scenarios. We’ll also briefly cover the various operations using the two classes.
2. BigDecimal
BigDecimal represents an immutable arbitrary-precision signed decimal number. It consists of two parts:
- Unscaled value – an arbitrary precision integer
- Scale – a 32-bit integer representing the number of digits to the right of the decimal point
For example, the BigDecimal 3.14 has the unscaled value of 314 and the scale of 2.
We use BigDecimal for high-precision arithmetic. We also use it for calculations requiring control over scale and rounding off behavior. One such example is calculations involving financial transactions.
We can create a BigDecimal object from String, character array, int, long, and BigInteger:
@Test
public void whenBigDecimalCreated_thenValueMatches() {
BigDecimal bdFromString = new BigDecimal("0.1");
BigDecimal bdFromCharArray = new BigDecimal(new char[] {'3','.','1','6','1','5'});
BigDecimal bdlFromInt = new BigDecimal(42);
BigDecimal bdFromLong = new BigDecimal(123412345678901L);
BigInteger bigInteger = BigInteger.probablePrime(100, new Random());
BigDecimal bdFromBigInteger = new BigDecimal(bigInteger);
assertEquals("0.1",bdFromString.toString());
assertEquals("3.1615",bdFromCharArray.toString());
assertEquals("42",bdlFromInt.toString());
assertEquals("123412345678901",bdFromLong.toString());
assertEquals(bigInteger.toString(),bdFromBigInteger.toString());
}
We can also create BigDecimal from double:
@Test
public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() {
BigDecimal bdFromDouble = new BigDecimal(0.1d);
assertNotEquals("0.1", bdFromDouble.toString());
}
However, the result, in this case, is different from expected (that is 0.1). This is because:
- the double constructor does an exact translation
- 0.1 does not have an exact representation in double
Therefore, we should use the String constructor instead of the double constructor.
In addition, we can convert double and long to BigDecimal using the valueOf static method:
@Test
public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() {
BigDecimal bdFromLong1 = BigDecimal.valueOf(123412345678901L);
BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2);
BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d);
assertEquals("123412345678901", bdFromLong1.toString());
assertEquals("1234123456789.01", bdFromLong2.toString());
assertEquals("0.1", bdFromDouble.toString());
}
This method converts double to its String representation before converting to BigDecimal. In addition, it may reuse object instances.
Hence, we should use the valueOf method in preference to the constructors.
3. Operations on BigDecimal
Just like the other Number classes (Integer, Long, Double etc.), BigDecimal provides operations for arithmetic and comparison operations. It also provides operations for scale manipulation, rounding and format conversion.
It does not overload the the arithmetic (+, -, /, *) or logical (>. < etc) operators. Instead, we use the corresponding methods – add, subtract, multiply, divide and compareTo.
BigDecimal has methods to extract various attributes, such as precision, scale, and sign:
@Test
public void whenGettingAttributes_thenExpectedResult() {
BigDecimal bd = new BigDecimal("-12345.6789");
assertEquals(9, bd.precision());
assertEquals(4, bd.scale());
assertEquals(-1, bd.signum());
}
We compare the value of two BigDecimals using the compareTo method:
@Test
public void whenComparingBigDecimals_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
BigDecimal bd3 = new BigDecimal("2.0");
assertTrue(bd1.compareTo(bd3) < 0);
assertTrue(bd3.compareTo(bd1) > 0);
assertTrue(bd1.compareTo(bd2) == 0);
assertTrue(bd1.compareTo(bd3) <= 0);
assertTrue(bd1.compareTo(bd2) >= 0);
assertTrue(bd1.compareTo(bd3) != 0);
}
This method ignores the scale while comparing.
On the other hand, the equals method considers two BigDecimal objects as equal only if they are equal in value and scale. Thus, BigDecimals 1.0 and 1.00 are not equal when compared by this method.
@Test
public void whenEqualsCalled_thenSizeAndScaleMatched() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
assertFalse(bd1.equals(bd2));
}
We perform arithmetic operations by calling the corresponding methods:
@Test
public void whenPerformingArithmetic_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("4.0");
BigDecimal bd2 = new BigDecimal("2.0");
BigDecimal sum = bd1.add(bd2);
BigDecimal difference = bd1.subtract(bd2);
BigDecimal quotient = bd1.divide(bd2);
BigDecimal product = bd1.multiply(bd2);
assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0);
assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(product.compareTo(new BigDecimal("8.0")) == 0);
}
Since BigDecimal is immutable, these operations do not modify the existing objects. Rather, they return new objects.
4. Rounding and BigDecimal
By rounding a number, we replace it with another having shorter, simpler and more meaningful representation. For example, we round $24.784917 to $24.78 as we do not have fractional cents.
The precision and rounding mode to be used varies depending on the calculation. For example, U.S. Federal Tax returns specify to round off to whole dollar amounts using HALF_UP.
There are two classes which control rounding behavior – RoundingMode and MathContext.
The enum RoundingMode provides eight rounding modes:
- CEILING – rounds towards positive infinity
- FLOOR – rounds towards negative infinity
- UP – rounds away from zero
- DOWN – rounds towards zero
- HALF_UP – rounds towards “nearest neighbor” unless both neighbors are equidistant, in which case rounds up
- HALF_DOWN – rounds towards “nearest neighbor” unless both neighbors are equidistant, in which case rounds down
- HALF_EVEN – rounds towards the “nearest neighbor” unless both neighbors are equidistant, in which case, rounds towards the even neighbor
- UNNECESSARY – no rounding is necessary and ArithmeticException is thrown if no exact result is possible
HALF_EVEN rounding mode minimizes the bias due to rounding operations. It is frequently used. It is also known as the banker’s rounding.
MathContext encapsulates both precision and rounding mode. There are few predefined MathContexts:
- DECIMAL32 – 7 digits precision and a rounding mode of HALF_EVEN
- DECIMAL64 – 16 digits precision and a rounding mode of HALF_EVEN
- DECIMAL128 – 34 digits precision and a rounding mode of HALF_EVEN
- UNLIMITED – unlimited precision arithmetic
Using this class, we can round a BigDecimal number using specified precision and rounding behavior:
@Test
public void whenRoundingDecimal_thenExpectedResult() {
BigDecimal bd = new BigDecimal("2.5");
// Round to 1 digit using HALF_EVEN
BigDecimal rounded = bd
.round(new MathContext(1, RoundingMode.HALF_EVEN));
assertEquals("2", rounded.toString());
}
Now, let’s examine the rounding concept using a sample calculation.
Let’s write a method to calculate the total amount to be paid for an item given a quantity and unit price. Let’s also apply a discount rate and sales tax rate. We round the final result to cents by using the setScale method:
public static BigDecimal calculateTotalAmount(BigDecimal quantity,
BigDecimal unitPrice, BigDecimal discountRate, BigDecimal taxRate) {
BigDecimal amount = quantity.multiply(unitPrice);
BigDecimal discount = amount.multiply(discountRate);
BigDecimal discountedAmount = amount.subtract(discount);
BigDecimal tax = discountedAmount.multiply(taxRate);
BigDecimal total = discountedAmount.add(tax);
// round to 2 decimal places using HALF_EVEN
BigDecimal roundedTotal = total.setScale(2, RoundingMode.HALF_EVEN);
return roundedTotal;
}
Now, let’s write a unit test for this method:
@Test
public void givenPurchaseTxn_whenCalculatingTotalAmount_thenExpectedResult() {
BigDecimal quantity = new BigDecimal("4.5");
BigDecimal unitPrice = new BigDecimal("2.69");
BigDecimal discountRate = new BigDecimal("0.10");
BigDecimal taxRate = new BigDecimal("0.0725");
BigDecimal amountToBePaid = BigDecimalDemo
.calculateTotalAmount(quantity, unitPrice, discountRate, taxRate);
assertEquals("11.68", amountToBePaid.toString());
}
5. BigInteger
BigInteger represents immutable arbitrary-precision integers. It is similar to the primitive integer types but allows arbitrary large values.
It is used when integers involved are larger than the limit of long type. For example, the factorial of 50 is 30414093201713378043612608166064768844377641568960512000000000000. This value is too big for an int or long data type to handle. It can only be stored in a BigInteger variable.
It is widely used in security and cryptography applications.
We can create BigInteger from a byte array or String:
@Test
public void whenBigIntegerCreatedFromConstructor_thenExpectedResult() {
BigInteger biFromString = new BigInteger("1234567890987654321");
BigInteger biFromByteArray = new BigInteger(
new byte[] { 64, 64, 64, 64, 64, 64 });
BigInteger biFromSignMagnitude = new BigInteger(-1,
new byte[] { 64, 64, 64, 64, 64, 64 });
assertEquals("1234567890987654321", biFromString.toString());
assertEquals("70644700037184", biFromByteArray.toString());
assertEquals("-70644700037184", biFromSignMagnitude.toString());
}
In addition, we can convert a long to BigInteger using the static method valueOf:
@Test
public void whenLongConvertedToBigInteger_thenValueMatches() {
BigInteger bi = BigInteger.valueOf(2305843009213693951L);
assertEquals("2305843009213693951", bi.toString());
}
6. Operations on BigInteger
Similar to int and long, BigInteger implements all the arithmetic and logical operations. But, it does not overload the operators.
It also implements the corresponding methods from Math class: abs, min, max, pow, signum.
We compare the value of two BigIntegers using the compareTo method:
@Test
public void givenBigIntegers_whentCompared_thenExpectedResult() {
BigInteger i = new BigInteger("123456789012345678901234567890");
BigInteger j = new BigInteger("123456789012345678901234567891");
BigInteger k = new BigInteger("123456789012345678901234567892");
assertTrue(i.compareTo(i) == 0);
assertTrue(j.compareTo(i) > 0);
assertTrue(j.compareTo(k) < 0);
}
We perform arithmetic operations by calling the corresponding methods:
@Test
public void givenBigIntegers_whenPerformingArithmetic_thenExpectedResult() {
BigInteger i = new BigInteger("4");
BigInteger j = new BigInteger("2");
BigInteger sum = i.add(j);
BigInteger difference = i.subtract(j);
BigInteger quotient = i.divide(j);
BigInteger product = i.multiply(j);
assertEquals(new BigInteger("6"), sum);
assertEquals(new BigInteger("2"), difference);
assertEquals(new BigInteger("2"), quotient);
assertEquals(new BigInteger("8"), product);
}
As BigInteger is immutable, these operations do not modify the existing objects. Unlike, int and long, these operations do not overflow.
BigInteger* has the bit operations similar to int and *long. But, we need to use the methods instead of operators:
@Test
public void givenBigIntegers_whenPerformingBitOperations_thenExpectedResult() {
BigInteger i = new BigInteger("17");
BigInteger j = new BigInteger("7");
BigInteger and = i.and(j);
BigInteger or = i.or(j);
BigInteger not = j.not();
BigInteger xor = i.xor(j);
BigInteger andNot = i.andNot(j);
BigInteger shiftLeft = i.shiftLeft(1);
BigInteger shiftRight = i.shiftRight(1);
assertEquals(new BigInteger("1"), and);
assertEquals(new BigInteger("23"), or);
assertEquals(new BigInteger("-8"), not);
assertEquals(new BigInteger("22"), xor);
assertEquals(new BigInteger("16"), andNot);
assertEquals(new BigInteger("34"), shiftLeft);
assertEquals(new BigInteger("8"), shiftRight);
}
It has additional bit manipulation methods:
@Test
public void givenBigIntegers_whenPerformingBitManipulations_thenExpectedResult() {
BigInteger i = new BigInteger("1018");
int bitCount = i.bitCount();
int bitLength = i.bitLength();
int getLowestSetBit = i.getLowestSetBit();
boolean testBit3 = i.testBit(3);
BigInteger setBit12 = i.setBit(12);
BigInteger flipBit0 = i.flipBit(0);
BigInteger clearBit3 = i.clearBit(3);
assertEquals(8, bitCount);
assertEquals(10, bitLength);
assertEquals(1, getLowestSetBit);
assertEquals(true, testBit3);
assertEquals(new BigInteger("5114"), setBit12);
assertEquals(new BigInteger("1019"), flipBit0);
assertEquals(new BigInteger("1010"), clearBit3);
}
BigInteger provides methods for GCD computation and modular arithmetic:
@Test
public void givenBigIntegers_whenModularCalculation_thenExpectedResult() {
BigInteger i = new BigInteger("31");
BigInteger j = new BigInteger("24");
BigInteger k = new BigInteger("16");
BigInteger gcd = j.gcd(k);
BigInteger multiplyAndmod = j.multiply(k).mod(i);
BigInteger modInverse = j.modInverse(i);
BigInteger modPow = j.modPow(k, i);
assertEquals(new BigInteger("8"), gcd);
assertEquals(new BigInteger("12"), multiplyAndmod);
assertEquals(new BigInteger("22"), modInverse);
assertEquals(new BigInteger("7"), modPow);
}
It also has methods related to prime generation and primality testing:
@Test
public void givenBigIntegers_whenPrimeOperations_thenExpectedResult() {
BigInteger i = BigInteger.probablePrime(100, new Random());
boolean isProbablePrime = i.isProbablePrime(1000);
assertEquals(true, isProbablePrime);
}
7. Conclusion
In this quick tutorial, we explored the classes BigDecimal and BigInteger. They are useful for advanced numerical computations where the primitive integer types do not suffice.
As usual, the full source code can be found over on GitHub.