skip to content

How do you correctly assert equality of floating-point values in JUnit 5, and why is a plain assertEquals on doubles dangerous?

level: middleimportance: should knowfreq 50%

answer

  1. assertEquals(expected, actual, delta)
  2. 0.1 + 0.2 != 0.3 (IEEE 754)
  3. delta = abs difference tolerance, domain-chosen
  4. NaN equals NaN under delta overload
  5. money -> BigDecimal compared via compareTo

basics

~10 s

Use the overload assertEquals(expected, actual, delta), where delta is a small tolerance. Floating-point math has rounding errors, so 0.1 + 0.2 isn't exactly 0.3. The delta says 'close enough'.

solid answer

~40 s

Doubles and floats can't represent most decimals exactly, so arithmetic accumulates tiny rounding errors — 0.1 + 0.2 yields 0.30000000000000004, not 0.3. An exact assertEquals(0.3, 0.1 + 0.2) therefore fails. JUnit 5 provides an overload assertEquals(double expected, double actual, double delta) (and a float version): the test passes when the absolute difference is within delta, the tolerance you choose for the problem's required precision. Pick delta to match the domain — e.g. 1e-9 for tight math, 0.01 for currency-ish rounding. There's also an optional message after the delta. As an alternative, for exact decimal arithmetic (money) prefer BigDecimal with assertEquals and be aware BigDecimal.equals also compares scale, so compareTo via assertEquals(0, a.compareTo(b)) is sometimes safer. The key interview point: never compare raw floating-point results with exact equality.

code

java · 19 lines
java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;

class FloatingPointAssertTest {
    @Test
    void exactComparisonFails() {
        // assertEquals(0.3, 0.1 + 0.2); // would FAIL: 0.30000000000000004
        assertEquals(0.3, 0.1 + 0.2, 1e-9); // tolerance -> passes
    }

    @Test
    void moneyUsesBigDecimalCompareTo() {
        BigDecimal a = new BigDecimal("1.0");
        BigDecimal b = new BigDecimal("1.00");
        assertNotEquals(a, b);                 // equals() compares scale -> not equal
        assertEquals(0, a.compareTo(b));       // numeric value equal -> passes
    }
}

go deeper

for a junior

Know that you should compare doubles with assertEquals(expected, actual, delta) and that exact float equality is unreliable.

for a middle

Explain WHY (IEEE 754 rounding, 0.1+0.2 example), that delta is an absolute tolerance, and how to size it to the domain.

for a senior

Discuss NaN handling under the delta overload, delta-too-small/too-large trade-offs, and BigDecimal-with-compareTo for exact decimal/money cases.

for a principal

Set team conventions: forbid raw double for money, standardize tolerance helpers, and treat numerical precision/representation as a design concern feeding test strategy.

## Why floating-point equality is hard Computers store `double` and `float` in **binary** following the IEEE 754 standard. Many everyday decimal fractions — like 0.1 — have **no exact finite binary representation**, just as 1/3 has no exact finite decimal. So the stored value is the nearest representable approximation, and arithmetic on these approximations accumulates tiny errors: ```java System.out.println(0.1 + 0.2); // 0.30000000000000004 ``` Because of this, an **exact** comparison is almost always wrong: ```java assertEquals(0.3, 0.1 + 0.2); // FAILS: 0.3 != 0.30000000000000004 ``` ## The delta overload JUnit 5 solves this with a third parameter called **delta**, a non-negative tolerance: ```java assertEquals(double expected, double actual, double delta) ``` The assertion passes when `Math.abs(expected - actual) <= delta`. In other words, delta means 'I accept any value within this distance of the expected one.' There is an equivalent `float` overload, and an optional trailing message/message-supplier argument: ```java assertEquals(0.3, 0.1 + 0.2, 1e-9); // passes assertEquals(0.3, 0.1 + 0.2, 1e-9, "sum mismatch"); // with message ``` ## Choosing delta Delta is a **domain decision**, not a magic constant: - Tight numerical code: `1e-9` or smaller. - Accumulated/iterative computations: larger, because errors compound. - A delta of `0.0` reduces to exact comparison — only valid when you genuinely expect bit-identical results (e.g. you stored and read back the same literal). Too small a delta gives flaky failures; too large hides real bugs. Pick the smallest tolerance the computation can guarantee. ## Special values With a delta, `assertEquals(Double.NaN, Double.NaN, 1e-9)` actually **passes** — JUnit treats two NaNs as equal here (unlike the `==` operator, where `NaN == NaN` is false). Infinities compare normally. ## The money alternative: BigDecimal For exact decimal arithmetic (currency), don't use `double` at all — use `java.math.BigDecimal`, which stores decimals exactly. But beware: `BigDecimal.equals` also compares **scale**, so `new BigDecimal("1.0").equals(new BigDecimal("1.00"))` is **false**. When you only care about numeric value, assert via `compareTo`: ```java assertEquals(0, new BigDecimal("1.0").compareTo(new BigDecimal("1.00"))); // passes ``` ## The one-line takeaway Never compare raw `double`/`float` results with exact equality — use the delta overload with a domain-appropriate tolerance, or switch to `BigDecimal` (compared via `compareTo`) when you need exact decimals.

  • What happens if you pass a delta of 0.0?
    It becomes an exact comparison again — the assertion passes only on bit-identical values. That's only appropriate when you truly expect identical results, like reading back the same stored literal; for computed results it reintroduces the flakiness.
  • Why might assertEquals on two BigDecimals fail even though they look numerically equal?
    BigDecimal.equals compares both value AND scale, so '1.0' and '1.00' are unequal. Use assertEquals(0, a.compareTo(b)) when you only care about numeric value.

saying these in an interview costs you the question

  • Comparing doubles with plain assertEquals and no delta
  • Treating delta as a fixed magic number rather than a precision choice
  • Using BigDecimal.equals for money where scale differs (1.0 vs 1.00)

context