BigInteger and BigDecimal in Java

Naveen AnamaneniUpdated · 6 min read

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

Exact.java▶ Try it
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));
    }
}
Output
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.3333

How it works

  1. BigDecimal arithmetic is exact: 0.1 + 0.2 is exactly 0.3. You call methods (add, subtract, multiply, divide) instead of using + and *.
  2. Create from a String, or with BigDecimal.valueOf(double). new BigDecimal(0.1) copies the binary error of the double into the BigDecimal.
  3. setScale(2, RoundingMode.HALF_UP) rounds to two decimal places, as invoices need.
  4. equals checks the scale too: 2.0 and 2.00 aren't equals. compareTo compares the numeric value: 0 means equal.
  5. Dividing 1 by 3 has no exact decimal answer, so divide without a scale throws. Give a scale and a rounding mode.

Rounding Modes

Mode2.345 → 2 places2.355 → 2 placesUse
HALF_UP2.352.36What most people learned at school; common on bills
HALF_EVEN2.342.36"Banker's rounding": ties go to the even digit, avoiding bias over many sums
DOWN2.342.35Truncate
UP2.352.36Always away from zero
CEILING / FLOORtoward +∞ / -∞Sign-aware

Huge Whole Numbers with BigInteger

Huge.java▶ Try it
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));
    }
}
Output
30! as long:       -8764578968847253504
30! as BigInteger: 265252859812191058636308480000000
2^100 = 1267650600228229401496703205376

30! 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:

MoneyCheck.java▶ Try it
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);
        }
    }
}
Output
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 refused

How it works

  1. The constructor normalizes the scale to 2 decimal places, so 2.0 and 2.00 become the same BigDecimal. That makes equals (which checks scale) behave the way money should.
  2. Money is final with private final fields; plus and percent return new objects.
  3. Currency is an enum (the next lessons cover enums fully), so currencies are compared safely with ==.
  4. equals and hashCode use 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

  1. Easy: Add up the prices "199.99", "49.50" and "0.51" with BigDecimal and print the exact total.
  2. Harder: Add a minus method and a split(int ways) method to Money. split divides the amount equally, rounding with HALF_EVEN; check with PASS/FAIL lines that splitting Rs. 100 three ways gives 33.33 each.

Quick check: BigDecimal and BigInteger

4 questions
1How should you create the BigDecimal value 0.1?
2new BigDecimal("2.0").compareTo(new BigDecimal("2.00")) returns:
3What happens with BigDecimal.ONE.divide(new BigDecimal("3"))?
4Which type holds 30! exactly?

Key Takeaways

Was this tutorial helpful?

Lesson 163 of 482