Why is 0.1 + 0.2 not 0.3, and what should you use for money?

Asked constantlyjunior0–8 yrs9 min read

Because a double stores binary fractions, and 0.1 has no exact binary form — it is really 0.1000000000000000055511151231257827…, so the sum lands just past 0.3. Use BigDecimal built from a String, or count in the smallest unit as a long. And know that BigDecimal.equals compares scale, so 1.0 does not equal 1.00 — use compareTo.

The Answer

  • double stores a binary fraction. 0.1 is not representable in binary, any more than 1/3 is in decimal, so it is stored as the nearest value it can hold — which is slightly more than a tenth.
  • Add two such approximations and the error survives: 0.1 + 0.2 is 0.30000000000000004.
  • This is not a Java bug. It is IEEE 754, and every language using hardware floats does the same.
  • For money use BigDecimal constructed from a String, or count in the smallest unit — paise, cents — as a long.
  • new BigDecimal(0.1) defeats the point: it faithfully copies the broken double. Use new BigDecimal("0.1") or BigDecimal.valueOf(0.1).
  • BigDecimal.equals compares value and scale, so 1.0 does not equal 1.00. Compare with compareTo(...) == 0.

Understand It

What the number actually is

A double is a binary fraction: a sum of halves, quarters, eighths. Some decimals land exactly on one — 0.5, 0.25, 0.75. Most do not. One tenth requires an infinitely repeating binary expansion, so the JVM stores the closest of the roughly 2⁶⁴ values it can represent.

You can see the stored value exactly, because new BigDecimal(double) copies it without rounding:

Compiled and run on this buildEdit and run
System.out.println("0.1 is really: " + new BigDecimal(0.1));
Output
0.1 is really: 0.1000000000000000055511151231257827021181583404541015625

That is the number Java has. Not a tenth — a tenth plus 5.55×10⁻¹⁸. System.out.println(0.1) prints 0.1 only because Double.toString prints the shortest decimal that round-trips to the same bits, which hides the difference until arithmetic exposes it.

Where the error shows up

Compiled and run on this buildEdit and run
System.out.println("0.1 + 0.2      = " + (0.1 + 0.2));
System.out.println("== 0.3         = " + (0.1 + 0.2 == 0.3));
System.out.println("1.03 - 0.42    = " + (1.03 - 0.42));
System.out.println("0.1 * 3        = " + (0.1 * 3));

double sum = 0;
for (int i = 0; i < 10; i++) sum += 0.1;
System.out.println("0.1 ten times  = " + sum);
System.out.println("== 1.0         = " + (sum == 1.0));
Output
0.1 + 0.2      = 0.30000000000000004
== 0.3         = false
1.03 - 0.42    = 0.6100000000000001
0.1 * 3        = 0.30000000000000004
0.1 ten times  = 0.9999999999999999
== 1.0         = false

1.03 - 0.42 is the one to notice. That is not a contrived example — it is a price minus a discount, and it produces 0.6100000000000001. Round it for display and it looks right; sum a million of them into a ledger and the total does not balance.

The last pair matters for a different reason: the error accumulates. Adding a tenth ten times lands below one, because each addition rounds. Any loop that totals currency in a double drifts, and the direction depends on the data.

Why new BigDecimal(0.1) is the wrong constructor

BigDecimal is exact, so people reach for it and then hand it a double — which has already lost the information:

Compiled and run on this buildEdit and run
System.out.println("new BigDecimal(0.1)     = "
    + new BigDecimal(0.1).setScale(20, RoundingMode.HALF_UP));
System.out.println("BigDecimal.valueOf(0.1) = " + BigDecimal.valueOf(0.1));
System.out.println("new BigDecimal(\"0.1\")   = " + new BigDecimal("0.1"));
Output
new BigDecimal(0.1)     = 0.10000000000000000555
BigDecimal.valueOf(0.1) = 0.1
new BigDecimal("0.1")   = 0.1

All three compile, and one of them is exactly the bug you were trying to fix. new BigDecimal(double) is documented as unpredictable for this reason. BigDecimal.valueOf(double) routes through Double.toString, so it gets the shortest round-tripping decimal — what you meant. new BigDecimal(String) never involves a double at all, and is the one to prefer when the value starts as text, which for money it usually does.

Then the arithmetic is exact:

Compiled and run on this buildEdit and run
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");

System.out.println("0.1 + 0.2   = " + a.add(b));
System.out.println("equals 0.3  = " + a.add(b).equals(new BigDecimal("0.3")));
Output
0.1 + 0.2   = 0.3
equals 0.3  = true

The BigDecimal trap nobody warns you about

BigDecimal carries a scale — the number of digits after the point — and equals compares it:

Compiled and run on this buildEdit and run
BigDecimal oneDecimal = new BigDecimal("1.0");
BigDecimal twoDecimals = new BigDecimal("1.00");

System.out.println("1.0 equals 1.00        = " + oneDecimal.equals(twoDecimals));
System.out.println("1.0 compareTo 1.00 = 0 = " + (oneDecimal.compareTo(twoDecimals) == 0));
Output
1.0 equals 1.00        = false
1.0 compareTo 1.00 = 0 = true

Same value, different scale, and equals says no. This is BigDecimal deliberately not being consistent with compareTo, and the class documents it.

The consequences are the ones from the comparison contract: a HashSet of BigDecimal treats 1.0 and 1.00 as two elements, a TreeSet treats them as one, and list.contains(new BigDecimal("1.00")) misses a stored 1.0. For money, compare with compareTo, and normalise the scale on the way in.

Reference

The two correct representations:

// 1. BigDecimal, from a String, with an explicit scale and rounding mode.
BigDecimal price = new BigDecimal("19.99");
BigDecimal qty   = new BigDecimal("3");
BigDecimal total = price.multiply(qty).setScale(2, RoundingMode.HALF_UP);

// Division REQUIRES a scale and mode — 1/3 has no exact decimal form,
// and without them it throws ArithmeticException rather than guessing.
BigDecimal share = total.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);

// 2. The smallest unit as a long. Fast, exact, and no scale to get wrong.
long paise = 1999L * 3;               // ₹19.99 x 3, in paise
String display = String.format("%d.%02d", paise / 100, paise % 100);

Which to reach for:

SituationUse
Money, invoices, tax, anything auditedBigDecimal from String
High-volume money, fixed currencylong of the smallest unit
Money crossing a databasematch the column: DECIMALBigDecimal
Physics, graphics, statistics, MLdouble — it is the right tool there
Comparing two BigDecimal valuescompareTo(...) == 0, never equals

Rules worth making automatic:

// WRONG — every one of these is a real bug
double total = 0.0;                        // money in a double
new BigDecimal(0.1);                       // double into an exact type
if (a.equals(b))                           // BigDecimal equality by scale
total.divide(count)                         // may throw: no scale given
if (Math.abs(x - y) < 0.0001)              // fine for physics, not for a ledger

// RIGHT
BigDecimal total = BigDecimal.ZERO;
total = total.add(lineAmount);              // BigDecimal is IMMUTABLE —
                                            // add() returns, it does not mutate
if (a.compareTo(b) == 0)
total.divide(count, 2, RoundingMode.HALF_UP)

RoundingMode.HALF_UP rounds 0.5 away from zero, which is what most people mean by "round". HALF_EVEN — banker's rounding — is the default in some financial contexts because it does not bias a long series upward. Pick deliberately and write it down; the default is no rounding at all, which is why divide throws.

Scenarios

A totals column off by one paisa. Each line rounds correctly for display while the running total accumulates in a double. The invoice looks right and reconciliation fails. The fix is not more rounding — it is not storing money in a double at any point in the chain.

An amount that survives a round trip and then does not. The database column is DECIMAL(10,2), the entity field is double. Reads and writes appear fine until a value needs more precision than the double preserves, and then the stored figure disagrees with the computed one. Match the column type.

A Set<BigDecimal> with apparent duplicates. 1.0 and 1.00 are distinct by equals, so both go in. Normalise with setScale(2, …) on entry, or key the set on something else.

Where double is right. Averages, percentages, coordinates, distances, model weights. It is fast, hardware-supported, and the tiny relative error is irrelevant when the input is already measured with more uncertainty than that. Money is different because the values are defined in decimal and the totals are audited.

Interviewer's Next Move

1. "Is this a Java bug?" No — IEEE 754 binary floating point, the same in C, Python, JavaScript and on the hardware itself. What is Java-specific is that Double.toString prints the shortest round-tripping decimal, so println(0.1) shows 0.1 and hides the discrepancy until arithmetic surfaces it.

2. "What is wrong with new BigDecimal(0.1)?" It faithfully copies a value that is already wrong — the double's exact binary expansion, 0.1000000000000000055…. Being exact about an inexact input is worse than useless. BigDecimal.valueOf(0.1) goes via Double.toString, and new BigDecimal("0.1") never touches a double.

3. "Why does divide throw?" Because a quotient like 1/3 has no terminating decimal form, so BigDecimal cannot represent it exactly and refuses to guess a precision. Supply a scale and a RoundingMode and it will comply. The exception is the class insisting you make the rounding decision explicitly.

4. "Why is 1.0 not equal to 1.00?" Because equals compares unscaled value and scale — 10×10⁻¹ against 100×10⁻². They are the same number with different precision, and BigDecimal treats precision as part of identity. compareTo compares numeric value only, which is why it returns 0. It is a deliberate, documented inconsistency with the usual equals/compareTo recommendation.

5. "long of cents, or BigDecimal?" long is faster, exact, and has no scale to mishandle — good for a single currency and high volume. BigDecimal handles multiple currencies with different minor units, arbitrary precision for tax and interest, and maps onto DECIMAL columns. Most business systems take BigDecimal and accept the cost.

Check Yourself

0.1 + 0.2 == 0.3 is false. Name the smallest change that makes the comparison meaningful. Stop comparing floats for equality: either use BigDecimal from strings and compareTo, or compare with a tolerance — which is acceptable for measurements and not for money, where the exact value is the point.

You replace double with BigDecimal and the bug persists. What did you probably write? new BigDecimal(someDouble). The value was already damaged before BigDecimal saw it, and the exact type preserved the damage. Build from a String, or from BigDecimal.valueOf.

Why does adding 0.1 ten times give less than 1.0, when 0.1 is stored as slightly more than a tenth? Because each addition rounds the running result to the nearest representable double, and those roundings do not all go the same way. Accumulated error is not the sum of the individual errors — which is exactly why totals in a double cannot be reasoned about.

Where this question goes next

Questions that lead here

  • What happens when an int overflows, and why does Math.abs sometimes return a negative number?

    Nothing happens — it wraps silently. Integer.MAX_VALUE + 1 is Integer.MIN_VALUE, with no exception and no warning. And because the range is asymmetric, MIN_VALUE has no positive counterpart, so Math.abs(Integer.MIN_VALUE) returns MIN_VALUE — a negative result from a method that promises a magnitude. Use long, or Math.addExact when you need it to fail loudly.

    Asked oftenjunior0–8 yrs9 min readLanguage basics

Every runnable example above was compiled and executed against openjdk 21.0.11 on this build, and its output diffed against what this page claims. Last updated 2026-09-11.