skip to content

BigDecimal: Precision, Scale & Rounding

BigDecimal models a value as an unscaled integer plus a scale, which is why it can represent money exactly where binary floating point cannot. Interviewers focus on the string-versus-double constructor trap, on divide throwing without a rounding mode, and on why HALF_EVEN is the banker's default.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

What is the difference between new BigDecimal("0.1"), new BigDecimal(0.1), and BigDecimal.valueOf(0.1)?

level: middleimportance: must knowfreq 71%

basics

~10 s

new BigDecimal("0.1") is exactly 0.1. new BigDecimal(0.1) takes the imprecise double and copies its error, giving 0.1000000000000000055... BigDecimal.valueOf(0.1) is safe — it produces exactly 0.1.

open as a page

How does scale behave through BigDecimal arithmetic, and why can divide throw an ArithmeticException?

level: seniorimportance: must knowfreq 68%

basics

~20 s

Each operation has predictable scale rules: multiply gives a scale equal to the sum of the operands' scales; add/subtract use the larger scale. divide can produce a non-terminating decimal (like 1/3), so without a rounding mode it throws ArithmeticException — you must supply a scale + RoundingMode or a MathContext.

open as a page

What are the BigDecimal RoundingModes, and why is HALF_EVEN (banker's rounding) often preferred for money?

level: seniorimportance: should knowfreq 62%

basics

~20 s

RoundingMode defines how to drop digits: HALF_UP rounds .5 away from zero (the schoolbook way), HALF_DOWN toward zero, and HALF_EVEN rounds .5 to the nearest even digit. HALF_EVEN is preferred for money because it removes the upward bias of always rounding .5 up.

open as a page

What is MathContext, how does it differ from setScale, and what do DECIMAL64 and UNLIMITED mean?

level: principalimportance: should knowfreq 41%

basics

~20 s

MathContext bundles a precision (total significant digits) plus a RoundingMode, and you pass it to operations to round by significant figures. setScale instead fixes the number of digits after the decimal point. DECIMAL64 is a 16-digit preset; UNLIMITED means no rounding (exact).

open as a page