skip to content

How does BigDecimal represent a number internally, and why does that design make scale significant to equals()?

level: middleimportance: should knowfreq 45%

answer

  1. value = unscaledValue × 10^(-scale)
  2. unscaledValue is a BigInteger, scale is an int
  3. 1.50 = unscaled 150, scale 2
  4. scale = precision = significant trailing zeros
  5. hashCode includes scale too

basics

~20 s

A BigDecimal is stored as a whole-number 'unscaled value' plus a 'scale' that says how many digits are after the decimal point. The value is unscaledValue × 10^(-scale). Because two equal numbers can have different scales (like 2.0 and 2.00), equals() treats them as different.

solid answer

~40 s

Internally a BigDecimal holds a BigInteger unscaled value and an int scale; the number equals unscaledValue × 10^(-scale). For example 1.50 is unscaled 150 with scale 2, while 1.5 is unscaled 15 with scale 1 — same value, different representation. Scale carries meaning: it records the number of decimal places, i.e. the precision, including significant trailing zeros. equals() is defined as representation equality, so it compares both unscaled value and scale; that's why 1.5 and 1.50 are unequal. This design is deliberate because in domains like money or measurement '1.50' (cents-precise) and '1.5' can be meaningfully different in precision. The trade-off is that equals() no longer means 'same number', so numeric comparisons must use compareTo().

code

java · 11 lines
java
BigDecimal x = new BigDecimal("1.50");
System.out.println(x.unscaledValue()); // 150
System.out.println(x.scale());         // 2
System.out.println(x.precision());     // 3 (significant digits)

BigDecimal y = new BigDecimal("1.5");
System.out.println(y.unscaledValue()); // 15
System.out.println(y.scale());         // 1

System.out.println(x.equals(y));       // false (different scale)
System.out.println(x.compareTo(y));    // 0     (same value)

go deeper

for a junior

Knows BigDecimal keeps track of how many decimal places a number has, which is why 2.0 and 2.00 can differ.

for a middle

Can state the unscaledValue × 10^(-scale) model, give worked examples, and explain why equals() factors in scale.

for a senior

Relates the representation model to the equals/hashCode contract and collection behavior, and knows setScale/stripTrailingZeros/precision distinctions.

for a principal

Can justify the design trade-off (precision-as-identity) against domain needs and define team rules for scale normalization in money/measurement domains.

## The two-part internal model `BigDecimal` is **not** a single floating-point number under the hood. It is a pair: 1. **unscaled value** — a `BigInteger` (an integer of unlimited size). Accessible via `unscaledValue()`. 2. **scale** — a plain `int`. Accessible via `scale()`. The number represented is exactly: ``` value = unscaledValue × 10^(-scale) ``` ### Worked examples | Literal | unscaledValue | scale | value | |---|---|---|---| | `new BigDecimal("1.5")` | 15 | 1 | 15 × 10⁻¹ = 1.5 | | `new BigDecimal("1.50")` | 150 | 2 | 150 × 10⁻² = 1.50 | | `new BigDecimal("150")` | 150 | 0 | 150 × 10⁰ = 150 | | `new BigDecimal("1.5E2")` | 15 | -1 | 15 × 10¹ = 150 | Note scale can be **negative** (it shifts the decimal point left, representing trailing zeros to the left of the point). ## Why scale is meaningful, not noise Scale encodes **precision** — specifically how many digits after the decimal point the value is known/expressed to. A measurement of `1.50 cm` claims accuracy to the hundredths; `1.5 cm` only to the tenths. A money amount `1.50` vs `1.5` may reflect cents vs no-cents formatting. So the trailing zero is **information**, not redundancy. Because that information is real, the designers made `equals()` **representation equality**: two BigDecimals are equal only if they have the same unscaled value **and** the same scale. This keeps equals() a true identity of state. ## The consequence If equals() honors scale, then `1.5` and `1.50` are unequal even though they're the same number. So: - `equals()` answers "same value *and* same precision?" - `compareTo() == 0` answers "same number?" This is why every BigDecimal tutorial warns: use `compareTo()` for numeric comparison. ## hashCode() consistency `hashCode()` is computed from both the unscaled value and the scale, so it is consistent with `equals()` (equal objects have equal hashes). That's good for the equals/hashCode contract — but it means `1.5` and `1.50` hash to different buckets, which is exactly why they behave surprisingly as map/set keys. ## Changing scale safely - `setScale(newScale, RoundingMode)` returns a new BigDecimal with a chosen scale, rounding if needed. - `stripTrailingZeros()` returns the value with as small a scale as possible (removes insignificant trailing zeros). - `precision()` returns the number of significant digits (different from scale). All BigDecimal operations are **immutable** — they return new instances; the original is never mutated. ## Key takeaway Scale is a first-class part of a BigDecimal's identity. equals() respects identity (value + scale); compareTo() respects mathematics (value only). Knowing the unscaled-value/scale model explains every surprising equality result.

  • Can a BigDecimal's scale be negative? What does that mean?
    Yes. A negative scale multiplies by a positive power of ten, representing trailing zeros to the left of the decimal point. For example 1.5E2 is unscaled 15 with scale -1, equal to 150.
  • What's the difference between scale() and precision()?
    scale() is the number of digits to the right of the decimal point; precision() is the total number of significant digits in the unscaled value. For 1.50, scale() is 2 and precision() is 3.

saying these in an interview costs you the question

  • Saying BigDecimal stores a double internally — it stores a BigInteger plus an int scale.
  • Claiming trailing zeros are always meaningless — scale encodes precision deliberately.
  • Thinking BigDecimal is mutable — all operations return new instances.
  • Confusing scale (digits after the point) with precision (total significant digits).

context