skip to content

Why should you use BigDecimal instead of double or float for monetary values?

level: juniorimportance: must knowfreq 78%

answer

  1. double = binary fraction, can't store 0.1 exactly
  2. 0.1 + 0.2 != 0.3 with double
  3. BigDecimal = unscaled integer × 10^-scale, base 10, exact
  4. Money: BigDecimal or integer cents, never double
  5. Cost: immutable + slower

basics

~10 s

double and float store numbers in binary, so many decimals like 0.1 can't be represented exactly and small errors creep in. BigDecimal stores decimals exactly, so it's the right choice for money.

solid answer

~40 s

double and float are IEEE-754 binary floating-point types. They represent values as a binary fraction times a power of two, so decimals that are not a sum of powers of two (like 0.1, 0.2, 0.3) cannot be stored exactly and are rounded to the nearest representable binary value. Those tiny errors accumulate across additions, multiplications and comparisons, which is unacceptable for money where every cent must be exact. BigDecimal instead stores an arbitrary-precision integer (the unscaled value) plus a scale (number of digits after the decimal point), so it represents decimal numbers exactly and lets you control rounding explicitly. The classic demonstration is 0.1 + 0.2 == 0.3 returning false with double, while BigDecimal('0.1').add(BigDecimal('0.2')) equals exactly 0.3. Use BigDecimal (or integer cents) for currency, taxes, and any exact decimal arithmetic.

code

java · 8 lines
java
double d = 0.1 + 0.2;
System.out.println(d);            // 0.30000000000000004
System.out.println(d == 0.3);     // false

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));                       // 0.3
System.out.println(a.add(b).compareTo(new BigDecimal("0.3")) == 0); // true

go deeper

for a junior

Knows double/float are imprecise for decimals and that BigDecimal (or cents) should be used for money; can cite the 0.1+0.2 example.

for a middle

Explains the IEEE-754 binary-fraction root cause and the unscaled-value/scale model of BigDecimal; knows the integer-cents alternative.

for a senior

Weighs BigDecimal vs integer minor units by use case, notes immutability/performance cost, and reserves double for scientific (relative-error-tolerant) work.

for a principal

Sets org-wide money-handling conventions (type, scale, rounding mode, currency policy), considers DB column types and serialization, and reasons about error accumulation at scale.

## The core problem: binary can't represent all decimals Computers store `double` and `float` using the **IEEE-754** standard. A value is stored as `sign × mantissa × 2^exponent` — that is, a *binary* fraction. A binary fraction can only represent numbers that are sums of powers of two: 1/2, 1/4, 1/8, 1/16, and so on. Many everyday decimals are **not** such sums. The number 0.1 in binary is `0.0001100110011...` repeating forever, exactly like 1/3 = 0.333... repeats in decimal. Since the storage has a fixed number of bits, the value is **rounded to the nearest representable binary number**. So the `double` you think is `0.1` is really about `0.1000000000000000055511151231257827021181583404541015625`. ### Why this matters for money These tiny errors are invisible in one number but **accumulate** across operations and break exact comparisons: ``` double a = 0.1 + 0.2; // 0.30000000000000004 a == 0.3; // false! ``` For money, exactness is mandatory: a billing system that is off by a fraction of a cent across millions of transactions is a real bug. You cannot ship that. ## How BigDecimal fixes it `java.math.BigDecimal` does **not** use binary floating-point. It stores two things: - an **unscaled value**: an arbitrary-precision integer (`BigInteger`) — it can be any size, no overflow. - a **scale**: the number of digits to the right of the decimal point. The actual number is `unscaledValue × 10^(-scale)`. For example `123.45` is stored as unscaled value `12345` with scale `2`. Because it works in **base 10**, every decimal you can write down is represented **exactly** — there is no rounding on construction (when you use the String constructor — see the pitfall question). ## Three terms to know - **Precision** = the total number of significant digits (digits in the unscaled value). `123.45` has precision 5. - **Scale** = digits after the decimal point. `123.45` has scale 2. - **Unscaled value** = the integer you get if you drop the decimal point. `123.45` → `12345`. ## The alternatives For money you have two correct choices: (1) **BigDecimal**, or (2) store **integer minor units** (e.g. cents as a `long`) and format on display. BigDecimal is more flexible (handles tax rates, percentages, currencies with three decimal places) and self-documents the scale; integer cents is faster and simpler when every amount has the same fixed scale. Both avoid binary float error. Never use `double`/`float` for currency. ## Tradeoffs of BigDecimal BigDecimal is **immutable** (every operation returns a new object) and considerably **slower** and more memory-hungry than primitive arithmetic. That is the price of exactness. For scientific computing where small relative error is fine, `double` is the correct, fast choice — BigDecimal is specifically for *exact decimal* needs.

  • Is storing money as a long of cents also acceptable?
    Yes — integer minor units avoid binary error and are fast/simple when every amount has a fixed scale. BigDecimal is preferred when scales vary (tax rates, multi-currency) or you need explicit rounding control.
  • Why is 0.5 representable exactly in double but 0.1 is not?
    0.5 is exactly 2^-1, a power of two, so it fits a binary fraction. 0.1 is not a finite sum of powers of two, so it repeats forever in binary and gets rounded.

saying these in an interview costs you the question

  • Saying double is fine if you round at the end (errors already accumulated)
  • Confusing the issue with display formatting rather than storage
  • Claiming float is more precise than double for decimals
  • Thinking BigDecimal is always the right choice including scientific code

context