BigInteger and BigDecimal in Java with Examples
Introduction
Java's primitive number types have two limits: integers overflow when they grow too large, and
(Note - The limits of the primitive types are explained in Java Primitive Data TypesClick to read tutorial: Java Primitive Data Types and Their Ranges with Examples.) BigInteger - whole numbers of any size A BigInteger holds an integer of any size, limited only by memory. Like StringClick to read tutorial: Java String Basics and Why Strings Are Immutable, it is immutable: there are no operators such as
OUTPUT of the above code
Explanation of the code
BigDecimal - exact decimal arithmetic
A BigDecimal stores a decimal number exactly, as an arbitrary-precision integer (the unscaled value) and a scale - the number of digits after the decimal point.
OUTPUT of the above code
Ten additions of 10 cents give exactly
OUTPUT of the above code
Explanation of the code
Scale, rounding and division
Adding, subtracting and multiplying
The most used rounding modes are
OUTPUT of the above code
Explanation of the code
OUTPUT of the above code
The zero check is a classic bug:
double cannot store most decimal fractions exactly - 0.1 + 0.2 is 0.30000000000000004. When results must be exact, Java provides two classes in java.math: BigInteger, for whole numbers of any size, and BigDecimal, for exact decimal arithmetic. This tutorial explains both with examples. We will start with BigInteger and its arithmetic methods. We will then use BigDecimal for money, see why it must be created from a String, and control rounding with scale and RoundingMode. Lastly, we will compare BigDecimal values correctly - equals() holds a surprise - and go through best practices.(Note - The limits of the primitive types are explained in Java Primitive Data TypesClick to read tutorial: Java Primitive Data Types and Their Ranges with Examples.) BigInteger - whole numbers of any size A BigInteger holds an integer of any size, limited only by memory. Like StringClick to read tutorial: Java String Basics and Why Strings Are Immutable, it is immutable: there are no operators such as
+ or * for it, and methods such as add() and multiply() return a new BigInteger, leaving the original unchanged.- Create one with
BigInteger.valueOf(long), ornew BigInteger("123456789012345678901234567890")for values beyondlong. - Ready-made constants:
BigInteger.ZERO,ONE,TWOandTEN. - Arithmetic:
add(),subtract(),multiply(),divide()(integer division),mod(),pow(),gcd(),abs(),negate(). - Comparison:
compareTo(),equals(),max(),min().
Calculating with BigInteger
//BigIntegerDemo.java
import java.math.BigInteger;
public class BigIntegerDemo {
static BigInteger factorial(int n) {
BigInteger result = BigInteger.ONE;
for (int i = 2; i <= n; i++) {
result = result.multiply(BigInteger.valueOf(i)); // the result must be reassigned
}
return result;
}
public static void main(String[] args) {
long f = 1;
for (int i = 2; i <= 21; i++) f *= i;
System.out.println("long max = " + Long.MAX_VALUE);
System.out.println("21! long = " + f + " (overflowed)");
System.out.println("21! exact = " + factorial(21));
System.out.println("25! = " + factorial(25));
System.out.println("2^100 = " + BigInteger.TWO.pow(100));
BigInteger a = new BigInteger("123456789012345678901234567890");
BigInteger b = new BigInteger("987654321098765432109876543210");
System.out.println("a + b = " + a.add(b));
System.out.println("b / a = " + b.divide(a) + " remainder " + b.mod(a));
System.out.println("gcd(a, b) = " + a.gcd(b));
System.out.println("a < b = " + (a.compareTo(b) < 0));
a.add(BigInteger.ONE); // result discarded - a is unchanged
System.out.println("a is still " + a);
System.out.println("a as long = " + a.longValue() + " (overflowed!)");
}
}
long max = 9223372036854775807 21! long = -4249290049419214848 (overflowed) 21! exact = 51090942171709440000 25! = 15511210043330985984000000 2^100 = 1267650600228229401496703205376 a + b = 1111111110111111111011111111100 b / a = 8 remainder 9000000000900000000090 gcd(a, b) = 9000000000900000000090 a < b = true a is still 123456789012345678901234567890 a as long = -4362896299872285998 (overflowed!)
- 21! is too large for a
long, which silently wrapped round to a negative number, whileBigIntegergave the exact answer.factorial(25)has 26 digits - far beyond the 19 digits of along. result = result.multiply(...)must reassign the result.a.add(BigInteger.ONE)on its own did nothing, becauseBigIntegeris immutable.compareTo()returns a negative number, zero or a positive number, just like String.compareTo()Click to read tutorial: Java String Comparison: == vs equals() vs compareTo().longValue()silently keeps only the low 64 bits of a value which does not fit. UselongValueExact(), which throws anArithmeticExceptioninstead.
19.99 is stored as 1999 with a scale of 2. It is immutable, and uses methods instead of operators, just like BigInteger. It is the standard type for money and any calculation where decimal results must be exact.
double vs BigDecimal for money
//MoneyDemo.java
import java.math.BigDecimal;
public class MoneyDemo {
public static void main(String[] args) {
double d = 0.1 + 0.2;
System.out.println("double 0.1 + 0.2 = " + d);
BigDecimal sum = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println("BigDecimal 0.1 + 0.2 = " + sum);
double total = 0;
for (int i = 0; i < 10; i++) total += 0.10;
System.out.println("ten x 0.10 as double = " + total);
BigDecimal exact = BigDecimal.ZERO;
BigDecimal tenCents = new BigDecimal("0.10");
for (int i = 0; i < 10; i++) exact = exact.add(tenCents);
System.out.println("ten x 0.10 BigDecimal = " + exact);
BigDecimal price = new BigDecimal("19.99");
BigDecimal quantity = BigDecimal.valueOf(3);
BigDecimal subtotal = price.multiply(quantity);
System.out.println("3 x 19.99 = " + subtotal);
System.out.println("unscaled " + subtotal.unscaledValue() + ", scale " + subtotal.scale());
}
}
double 0.1 + 0.2 = 0.30000000000000004 BigDecimal 0.1 + 0.2 = 0.3 ten x 0.10 as double = 0.9999999999999999 ten x 0.10 BigDecimal = 1.00 3 x 19.99 = 59.97 unscaled 5997, scale 2
1.00 with BigDecimal, but 0.9999999999999999 with double - an error which, in an accounting system, never goes away.
Create BigDecimal from a String, not a double
new BigDecimal(0.1) looks reasonable, but the double 0.1 is already inexact before BigDecimal sees it - and BigDecimal faithfully stores the inexact value, all 55 digits of it.
Three ways to create a BigDecimal
//CreatingBigDecimal.java
import java.math.BigDecimal;
public class CreatingBigDecimal {
public static void main(String[] args) {
System.out.println("new BigDecimal(0.1) = " + new BigDecimal(0.1));
System.out.println("new BigDecimal(\"0.1\") = " + new BigDecimal("0.1"));
System.out.println("BigDecimal.valueOf(0.1) = " + BigDecimal.valueOf(0.1));
System.out.println("BigDecimal.valueOf(5) = " + BigDecimal.valueOf(5));
System.out.println("valueOf(1999, 2) = " + BigDecimal.valueOf(1999, 2));
}
}
new BigDecimal(0.1) = 0.1000000000000000055511151231257827021181583404541015625
new BigDecimal("0.1") = 0.1
BigDecimal.valueOf(0.1) = 0.1
BigDecimal.valueOf(5) = 5
valueOf(1999, 2) = 19.99
new BigDecimal(0.1)shows the exact binary value of thedoubleclosest to 0.1. Never use thedoubleconstructor for amounts.new BigDecimal("0.1")is exact, and is the best choice for values from user input, files or databases.BigDecimal.valueOf(double)converts via thedouble's shortest decimal text, sovalueOf(0.1)gives0.1. It is acceptable when you only have adouble.valueOf(1999, 2)builds a value from an unscaled integer and a scale - handy when amounts are stored in cents.
BigDecimal values is always exact. Division is different: 1 divided by 3 has no exact decimal representation, so divide() with no rounding information throws an ArithmeticException. Pass a scale and a RoundingMode, or a MathContext giving the number of significant digits. setScale() rounds any value to a given number of decimal places.The most used rounding modes are
HALF_UP (halves round away from zero - the school rule), HALF_EVEN (halves round to the even neighbour - banker's rounding, the default in many financial systems), DOWN (towards zero) and UP (away from zero).
Division, setScale() and RoundingMode
//RoundingBigDecimal.java
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class RoundingBigDecimal {
public static void main(String[] args) {
BigDecimal total = new BigDecimal("100.00");
BigDecimal people = BigDecimal.valueOf(3);
try {
total.divide(people);
} catch (ArithmeticException e) {
System.out.println("divide() without rounding: " + e.getMessage());
}
System.out.println("divide, 2 places HALF_UP : " + total.divide(people, 2, RoundingMode.HALF_UP));
System.out.println("divide, 10 sig. digits : " + total.divide(people, new MathContext(10)));
BigDecimal[] values = {new BigDecimal("2.345"), new BigDecimal("2.355"), new BigDecimal("-2.345")};
for (BigDecimal v : values) {
System.out.printf("%6s -> HALF_UP %5s, HALF_EVEN %5s, DOWN %5s%n", v,
v.setScale(2, RoundingMode.HALF_UP), v.setScale(2, RoundingMode.HALF_EVEN), v.setScale(2, RoundingMode.DOWN));
}
BigDecimal price = new BigDecimal("49.99");
BigDecimal taxRate = new BigDecimal("0.18");
BigDecimal tax = price.multiply(taxRate).setScale(2, RoundingMode.HALF_UP);
System.out.println("price " + price + " + tax " + tax + " = " + price.add(tax));
}
}
divide() without rounding: Non-terminating decimal expansion; no exact representable decimal result. divide, 2 places HALF_UP : 33.33 divide, 10 sig. digits : 33.33333333 2.345 -> HALF_UP 2.35, HALF_EVEN 2.34, DOWN 2.34 2.355 -> HALF_UP 2.36, HALF_EVEN 2.36, DOWN 2.35 -2.345 -> HALF_UP -2.35, HALF_EVEN -2.34, DOWN -2.34 price 49.99 + tax 9.00 = 58.99
- 100.00 / 3 cannot be written exactly, so plain
divide()threw "Non-terminating decimal expansion". 2.345and2.355differ underHALF_EVEN: each half goes to the neighbour whose last digit is even (2.34 and 2.36), which keeps rounding unbiased over many values.- The tax was rounded to cents with
setScale(2, RoundingMode.HALF_UP)before being added - decide where rounding happens, and do it explicitly.
BigDecimal.equals() compares the value and the scale, so 2.0 and 2.00 are not equals(), although they are the same number. To compare numeric values, use compareTo(). This also matters for collections: a HashSet uses equals(), while a TreeSet uses compareTo().
equals() vs compareTo() for BigDecimal
//CompareBigDecimal.java
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
public class CompareBigDecimal {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");
System.out.println("a.equals(b) = " + a.equals(b));
System.out.println("a.compareTo(b) == 0 = " + (a.compareTo(b) == 0));
System.out.println("b.stripTrailingZeros = " + b.stripTrailingZeros());
List<BigDecimal> amounts = List.of(a, b);
System.out.println("HashSet size: " + new HashSet<>(amounts).size() + ", TreeSet size: " + new TreeSet<>(amounts).size());
BigDecimal balance = new BigDecimal("0.00");
System.out.println("balance is zero (equals) : " + balance.equals(BigDecimal.ZERO));
System.out.println("balance is zero (compareTo) : " + (balance.compareTo(BigDecimal.ZERO) == 0));
System.out.println("balance is zero (signum) : " + (balance.signum() == 0));
}
}
a.equals(b) = false a.compareTo(b) == 0 = true b.stripTrailingZeros = 2 HashSet size: 2, TreeSet size: 1 balance is zero (equals) : false balance is zero (compareTo) : true balance is zero (signum) : true
balance.equals(BigDecimal.ZERO) is false for 0.00. Use compareTo() or signum().
Best practices
- Use BigDecimal for money, never
floatordouble. An alternative is alongholding the amount in the smallest unit, such as cents. - Create from Strings -
new BigDecimal("19.99")- or fromvalueOf(); never from thedoubleconstructor. - Always give division a scale and RoundingMode, and round explicitly with
setScale()where your business rules say so. - Compare with compareTo(), and check for zero with
signum(). - Reassign results - both classes are immutable, so
total.add(x);on its own does nothing. - Print plainly -
toString()can use scientific notation for some values (1E+3);toPlainString()never does. - Mind the cost - these classes are much slower than primitives. Use them where exactness matters, not for every number.
BigInteger for whole numbers of any size, with its arithmetic methods, and saw that it is immutable. We then used BigDecimal for exact decimal arithmetic, and saw ten additions of 10 cents come out exactly right where double did not. Next we saw why a BigDecimal must be created from a String or valueOf(), and controlled division and rounding with scale, RoundingMode and MathContext. We then compared values with compareTo() instead of equals(), which also compares scale. Lastly, we went through best practices. Next, we move on to exception handling with checked vs unchecked exceptionsClick to read tutorial: Checked vs Unchecked Exceptions in Java.