skip to content

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

level: seniorimportance: must knowfreq 68%

answer

  1. Immutable — use the return value
  2. add/subtract → max scale; multiply → sum of scales; pow → scale × n
  3. divide with no rounding throws on non-terminating result
  4. Fix: divide(b, scale, RoundingMode) or divide(b, MathContext)
  5. equals compares scale too; use compareTo for value; setScale at the end

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.

solid answer

~50 s

BigDecimal is immutable, so every operation returns a new value with a scale determined by fixed rules. Addition and subtraction produce a result whose scale is the maximum of the operands' scales. Multiplication produces a scale equal to the sum of the operands' scales (2.00 × 3.000 → scale 5). pow multiplies the scale by the exponent. Division is the exception: the exact quotient may be a non-terminating decimal — 1 divided by 3 is 0.333... forever — and BigDecimal refuses to guess how to round. So divide(other) with no rounding instruction throws ArithmeticException ('Non-terminating decimal expansion; no exact representable decimal result') whenever the result doesn't terminate. The fix is to tell it how: divide(other, scale, RoundingMode) or divide(other, MathContext), which round to a finite result. There is also remainder and divideAndRemainder for the integer-division pair. Because results carry these scales, you typically setScale at the end for display/storage.

code

java · 10 lines
java
BigDecimal one = BigDecimal.ONE;
BigDecimal three = new BigDecimal(3);
// one.divide(three);                 // throws ArithmeticException
System.out.println(one.divide(three, 4, RoundingMode.HALF_UP)); // 0.3333

BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("3.000");
System.out.println(a.multiply(b));            // 6.00000  (scale 5)
System.out.println(a.add(new BigDecimal("1.5"))); // 3.50  (scale 2 = max)
System.out.println(a.equals(b.subtract(new BigDecimal("1.000")))); // false (scale differs)

go deeper

for a junior

Knows BigDecimal is immutable and that divide can throw if you don't give a rounding mode.

for a middle

States the add/subtract/multiply scale rules and fixes a failing divide with divide(b, scale, RoundingMode); knows equals vs compareTo.

for a senior

Explains non-terminating expansion precisely, chooses scale-based vs MathContext division by domain, and applies the 'full precision then setScale once' money pattern.

for a principal

Defines arithmetic/rounding conventions across a codebase, audits for intermediate-rounding bias and equals-misuse, and reasons about scale growth in long calculation chains.

## Immutability first Every `BigDecimal` is **immutable** — `a.add(b)` does not change `a`; it returns a brand-new BigDecimal. A classic bug is calling `value.setScale(2, …)` or `value.add(x)` and ignoring the return value, expecting `value` to change. It never does. ## The scale model recap A BigDecimal is `unscaledValue × 10^(-scale)`. **Scale** = digits after the decimal point; **precision** = total significant digits. `2.00` has scale 2 and precision 3 (yes, trailing zeros count toward both scale and precision). Operations preserve scale according to fixed rules so results are deterministic. ## Scale rules per operation - **add / subtract**: result scale = **max** of the two operand scales. `2.5 + 2.50` → `5.00` (scale 2). The values are aligned to the larger scale first. - **multiply**: result scale = **sum** of the two operand scales. `2.00 (scale 2) × 3.000 (scale 3)` → `6.00000` (scale 5). This is why repeated multiplication grows scale fast. - **pow(n)**: scale = operand scale × n. - **divide**: see below — special. ## Why divide throws ArithmeticException The **exact** result of a division may have a **non-terminating decimal expansion**. `BigDecimal.ONE.divide(new BigDecimal(3))` would be `0.3333...` forever. Since BigDecimal must store a *finite* exact value, and you gave it no instruction on how to round, it cannot produce a correct answer — so it throws: ``` java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result ``` Importantly, `1.divide(2)` does **not** throw — 0.5 terminates. The exception fires only when the exact quotient doesn't terminate **and** you used the no-rounding overload. ### The fix: tell divide how to round Two families of overloads make division safe: ```java // 1) explicit scale + rounding mode BigDecimal r = one.divide(three, 4, RoundingMode.HALF_UP); // 0.3333 // 2) a MathContext (precision + rounding) BigDecimal r2 = one.divide(three, new MathContext(5)); // 0.33333 ``` Overload (1) fixes the **scale** (digits after the point). Overload (2) uses a `MathContext`, which fixes the **precision** (total significant digits) and a rounding mode. Choose scale-based for money (fixed decimal places); precision-based for significant-figure work. ## remainder, divideAndRemainder, pow - `a.remainder(b)` gives the remainder of integer division (`a - a.divideToIntegralValue(b) * b`); its sign follows the dividend. - `a.divideAndRemainder(b)` returns a 2-element array `[quotient, remainder]` in one pass. - `a.pow(n)` raises to an integer power (exact unless you pass a MathContext). ## stripTrailingZeros and equals vs compareTo Because scale is part of identity, `new BigDecimal("2.0").equals(new BigDecimal("2.00"))` is **false** — same value, different scale. Use `compareTo() == 0` to compare by numeric value. `stripTrailingZeros()` removes trailing zeros (`2.00` → `2E+2`? careful — it can yield exponential form for trailing zeros before the point); often paired with `setScale` for clean display. ## Practical pattern for money Do the arithmetic at full precision, then **`setScale(2, RoundingMode.HALF_UP)`** (or HALF_EVEN) once at the end for storage/display. Don't round intermediate steps unless the domain requires it.

  • Does 1.divide(2) throw ArithmeticException?
    No — 0.5 terminates, so the no-rounding divide returns 0.5 fine. The exception only fires when the exact quotient is non-terminating (e.g. 1/3) and no rounding was specified.
  • Why does equals(2.0, 2.00) return false but compareTo returns 0?
    equals considers both value AND scale, so different scales are unequal. compareTo ignores scale and compares only the numeric value, so it returns 0 (equal).
  • What scale does 2.50 × 1.2 have?
    Scale 3 (sum of scales 2 + 1): the result is 3.000.

saying these in an interview costs you the question

  • Expecting a.setScale(...) to mutate a in place
  • Thinking divide always throws (it only throws on non-terminating exact results)
  • Using equals() to compare numeric value (2.0 vs 2.00 → false)
  • Rounding every intermediate step and accumulating bias

context