Why is 0.1 + 0.2 not 0.3, and what should you use for money?
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
doublestores a binary fraction.0.1is not representable in binary, any more than1/3is 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.2is0.30000000000000004. - This is not a Java bug. It is IEEE 754, and every language using hardware floats does the same.
- For money use
BigDecimalconstructed from aString, or count in the smallest unit — paise, cents — as along. new BigDecimal(0.1)defeats the point: it faithfully copies the broken double. Usenew BigDecimal("0.1")orBigDecimal.valueOf(0.1).BigDecimal.equalscompares value and scale, so1.0does not equal1.00. Compare withcompareTo(...) == 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:
System.out.println("0.1 is really: " + new BigDecimal(0.1));0.1 is really: 0.1000000000000000055511151231257827021181583404541015625That 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
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));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 = false1.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:
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"));new BigDecimal(0.1) = 0.10000000000000000555
BigDecimal.valueOf(0.1) = 0.1
new BigDecimal("0.1") = 0.1All 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:
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")));0.1 + 0.2 = 0.3
equals 0.3 = trueThe BigDecimal trap nobody warns you about
BigDecimal carries a scale — the number of digits after the point — and
equals compares it:
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));1.0 equals 1.00 = false
1.0 compareTo 1.00 = 0 = trueSame 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:
| Situation | Use |
|---|---|
| Money, invoices, tax, anything audited | BigDecimal from String |
| High-volume money, fixed currency | long of the smallest unit |
| Money crossing a database | match the column: DECIMAL → BigDecimal |
| Physics, graphics, statistics, ML | double — it is the right tool there |
Comparing two BigDecimal values | compareTo(...) == 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.