SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: new BigDecimal(0.1) is not 0.1; BigDecimal.valueOf(0.1) is

· by Serguey Asael Shinder / Serguey Shinder

BigDecimal is the type people reach for precisely to escape floating-point error, which is what makes its double constructor such a sharp trap: it imports the error instead of removing it.

new BigDecimal(0.1);        // 0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1);    // 0.1
new BigDecimal("0.1");      // 0.1
Serguey Asael Shinder: new BigDecimal(0.1) is not 0.1; BigDecimal.valueOf(0.1) is
new BigDecimal(0.1) is not 0.1; BigDecimal.valueOf(0.1) is — Serguey Asael Shinder

All three look like they say "0.1." Only the first is lying, and it is lying faithfully. The value 0.1 cannot be represented exactly as a double, so the literal you wrote is already the nearest binary approximation before BigDecimal ever sees it. new BigDecimal(double) then faithfully records that number to its full extent — 55 digits of it. The javadoc for the constructor says this directly: its results "can be somewhat unpredictable," because the double "will be exactly equal to 0.1... notwithstanding the fact that 0.1 cannot be represented exactly," and it recommends the String constructor or valueOf when the value is one you can write as a literal. (See https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html#%3Cinit%3E(double).) BigDecimal.valueOf(double) is documented as being "generally" the canonical translation, because it routes through Double.toString, which gives you the short decimal you expect.

The bug this produces survives every casual test. new BigDecimal(0.1).add(new BigDecimal("0.2")) is not 0.3, and worse, new BigDecimal(0.1).equals(BigDecimal.valueOf(0.1)) is false — the two differ far below any scale you print, so logging shows "0.1" on both sides of a comparison that returns false. It detonates in equality checks, in compareTo against a parsed value, and anywhere a total is summed from double-constructed decimals and checked against an expected figure.

The rule is mechanical and worth enforcing with a lint if your codebase handles money: never pass a double to the BigDecimal constructor. Build decimals from a String when the value is a literal, and from BigDecimal.valueOf when it arrives as a double you cannot change. The only reason to use new BigDecimal(double) is the rare case where you deliberately want the exact binary value a double holds — and if that is what you want, a comment saying so is cheaper than the afternoon the next reader spends proving the arithmetic is "wrong."