BigInteger and BigDecimal in Java
0.1 + 0.2 in Java prints 0.30000000000000004. That's not a Java bug: double stores numbers in binary, and most decimal fractions can't be stored exactly. For a scientific calculation the tiny error doesn't matter; for a bank balance or a GST invoice it's unacceptable. BigDecimal stores decimal numbers exactly, and BigInteger stores whole numbers of any size, far beyond long. Both live in java.math.
Exact Decimals with BigDecimal
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Exact {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
BigDecimal tenth = new BigDecimal("0.1");
System.out.println(tenth.add(new BigDecimal("0.2")));
System.out.println(new BigDecimal(0.1));
System.out.println(BigDecimal.valueOf(0.1));
BigDecimal price = new BigDecimal("1499.50");
BigDecimal gst = price.multiply(new BigDecimal("0.18"))
.setScale(2, RoundingMode.HALF_UP);
System.out.println("GST " + gst + ", total " + price.add(gst));
BigDecimal two = new BigDecimal("2.0");
BigDecimal twoAgain = new BigDecimal("2.00");
System.out.println(two.equals(twoAgain));
System.out.println(two.compareTo(twoAgain));
try {
BigDecimal.ONE.divide(new BigDecimal("3"));
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
System.out.println(BigDecimal.ONE.divide(new BigDecimal("3"),
4, RoundingMode.HALF_UP));
}
}0.30000000000000004
0.3
0.1000000000000000055511151231257827021181583404541015625
0.1
GST 269.91, total 1769.41
false
0
Non-terminating decimal expansion; no exact representable decimal result.
0.3333How it works
BigDecimalarithmetic is exact:0.1 + 0.2is exactly0.3. You call methods (add,subtract,multiply,divide) instead of using+and*.- Create from a
String, or withBigDecimal.valueOf(double).new BigDecimal(0.1)copies the binary error of thedoubleinto theBigDecimal. setScale(2, RoundingMode.HALF_UP)rounds to two decimal places, as invoices need.equalschecks the scale too:2.0and2.00aren'tequals.compareTocompares the numeric value:0means equal.- Dividing 1 by 3 has no exact decimal answer, so
dividewithout a scale throws. Give a scale and a rounding mode.
Rounding Modes
| Mode | 2.345 → 2 places | 2.355 → 2 places | Use |
|---|---|---|---|
HALF_UP | 2.35 | 2.36 | What most people learned at school; common on bills |
HALF_EVEN | 2.34 | 2.36 | "Banker's rounding": ties go to the even digit, avoiding bias over many sums |
DOWN | 2.34 | 2.35 | Truncate |
UP | 2.35 | 2.36 | Always away from zero |
CEILING / FLOOR | toward +∞ / -∞ | Sign-aware |
Huge Whole Numbers with BigInteger
import java.math.BigInteger;
public class Huge {
public static void main(String[] args) {
long asLong = 1;
BigInteger exact = BigInteger.ONE;
for (int i = 2; i <= 30; i++) {
asLong *= i;
exact = exact.multiply(BigInteger.valueOf(i));
}
System.out.println("30! as long: " + asLong);
System.out.println("30! as BigInteger: " + exact);
System.out.println("2^100 = " + BigInteger.TWO.pow(100));
}
}30! as long: -8764578968847253504
30! as BigInteger: 265252859812191058636308480000000
2^100 = 126765060022822940149670320537630! overflows long silently and even turns negative; BigInteger gives the exact 33-digit answer. It's used for factorials, cryptography (huge primes) and any count that can outgrow 9.2 × 10¹⁸.
Putting It Together: An Immutable Money
This chapter's checkpoint combines immutability, BigDecimal, an enum and a correct equals and hashCode, then checks every rule from main:
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
public class MoneyCheck {
enum Currency { INR, USD }
static final class Money {
private final BigDecimal amount;
private final Currency currency;
Money(String amount, Currency currency) {
this(new BigDecimal(amount), currency);
}
Money(BigDecimal amount, Currency currency) {
this.amount = Objects.requireNonNull(amount)
.setScale(2, RoundingMode.HALF_EVEN);
this.currency = Objects.requireNonNull(currency);
}
Money plus(Money other) {
if (currency != other.currency) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(amount.add(other.amount), currency);
}
Money percent(String rate) {
BigDecimal part = amount.multiply(new BigDecimal(rate))
.divide(BigDecimal.valueOf(100));
return new Money(part, currency);
}
@Override
public boolean equals(Object o) {
return o instanceof Money m
&& amount.equals(m.amount) && currency == m.currency;
}
@Override
public int hashCode() { return Objects.hash(amount, currency); }
@Override
public String toString() { return currency + " " + amount; }
}
static void check(String rule, boolean ok) {
System.out.println((ok ? "PASS " : "FAIL ") + rule);
}
public static void main(String[] args) {
Currency inr = Currency.INR;
Money price = new Money("1499.50", inr);
Money gst = price.percent("18");
System.out.println(price + " + GST " + gst + " = " + price.plus(gst));
Money sum = new Money("0.1", inr).plus(new Money("0.2", inr));
check("0.1 + 0.2 is exactly 0.30", sum.equals(new Money("0.30", inr)));
Money a = new Money("2.0", inr);
Money b = new Money("2.00", inr);
check("2.0 equals 2.00", a.equals(b));
check("equal objects have equal hash codes",
a.hashCode() == b.hashCode());
check("INR and USD differ",
!new Money("5", inr).equals(new Money("5", Currency.USD)));
check("price unchanged by plus",
price.equals(new Money("1499.50", inr)));
try {
price.plus(new Money("10", Currency.USD));
check("mixing currencies is refused", false);
} catch (IllegalArgumentException e) {
check("mixing currencies is refused", true);
}
}
}INR 1499.50 + GST INR 269.91 = INR 1769.41
PASS 0.1 + 0.2 is exactly 0.30
PASS 2.0 equals 2.00
PASS equal objects have equal hash codes
PASS INR and USD differ
PASS price unchanged by plus
PASS mixing currencies is refusedHow it works
- The constructor normalizes the scale to 2 decimal places, so
2.0and2.00become the sameBigDecimal. That makesequals(which checks scale) behave the way money should. Moneyisfinalwithprivate finalfields;plusandpercentreturn new objects.Currencyis an enum (the next lessons cover enums fully), so currencies are compared safely with==.equalsandhashCodeuse the same two fields, keeping the contract.
Common Mistakes
new BigDecimal(0.1). Use new BigDecimal("0.1") or BigDecimal.valueOf(0.1).
Comparing with equals. Use compareTo(...) == 0, or normalize the scale first, as Money does.
Ignoring the result. BigDecimal is immutable: price.add(gst); alone does nothing; write price = price.add(gst);.
Interview Corner
Why should you use BigDecimal instead of double for money?
double is binary floating point, so most decimal fractions such as 0.1 can't be represented exactly, and errors accumulate in sums. BigDecimal represents decimal numbers exactly with an unscaled integer and a scale, and gives full control over rounding through RoundingMode, which financial calculations require.
Why does new BigDecimal("2.0").equals(new BigDecimal("2.00")) return false?
BigDecimal.equals considers both the value and the scale, and 2.0 has scale 1 while 2.00 has scale 2. compareTo compares only the numeric value and returns 0 for them. Use compareTo for numeric equality, or normalize scales before using equals or putting values in hash-based collections.
Practice
- Easy: Add up the prices
"199.99","49.50"and"0.51"withBigDecimaland print the exact total. - Harder: Add a
minusmethod and asplit(int ways)method toMoney.splitdivides the amount equally, rounding withHALF_EVEN; check with PASS/FAIL lines that splitting Rs. 100 three ways gives 33.33 each.
Key Takeaways
Was this tutorial helpful?