skip to content

How does unittest.TestCase.assertAlmostEqual compare floats, and when do you pass delta?

level: middleimportance: should knowfreq 38%

answer

  1. Floats are not exact, so tests need slack
  2. Rounding a difference, not scaling it
  3. Seven of something by default
  4. Two knobs that cannot be combined
  5. At a billion the default is exact equality

basics

~20 s

assertAlmostEqual rounds the difference between the two values to seven decimal places and checks it is zero — an absolute tolerance. places= changes the digit count; delta= gives an explicit absolute bound instead. Passing both raises TypeError.

solid answer

~40 s

`TestCase.assertAlmostEqual(a, b)` passes if the values compare equal, or if `round(a - b, 7) == 0` — an **absolute** tolerance of about `5e-8`, expressed in decimal places. `places=N` changes the number of places; `delta=D` replaces the whole scheme with `abs(a - b) <= D`; passing both raises `TypeError`. The trap is that the default is absolute and therefore does not scale: `assertAlmostEqual(0.1 + 0.2, 0.3)` passes, while `assertAlmostEqual(1_000_000.4, 1_000_000.6)` fails, and near `1e9` the gap between adjacent doubles already exceeds the default tolerance, so the assertion has silently become exact equality. Use `delta=` whenever the quantity has a scale and a real error budget. For a relative tolerance, `unittest` has nothing — use `math.isclose(a, b, rel_tol=...)` inside an `assertTrue` with a `msg=` carrying both values.

code

python · 9 lines
python
import unittest

tc = unittest.TestCase()
tc.assertAlmostEqual(0.1 + 0.2, 0.3)
tc.assertAlmostEqual(1_000_000.4, 1_000_000.6, delta=0.5)
try:
    tc.assertAlmostEqual(1_000_000.4, 1_000_000.6)
except AssertionError as exc:
    print(exc)

go deeper

for a junior

Know that computed floats are rarely exactly equal, so 0.1 + 0.2 == 0.3 is False, and that unittest offers a tolerance-based assertion for exactly that situation.

for a middle

Explain the mechanics: the default rounds the difference to seven decimal places, places= changes that count, delta= is a plain absolute bound, and the two cannot be passed together.

for a senior

Demonstrate that you pick a tolerance from the domain rather than accepting the default, and can say why an absolute default becomes exact equality at large magnitudes and when relative tolerance is the right model.

for a principal

Own the numerical-testing convention: which quantities get an error budget, whether comparisons are absolute or relative, and how to keep tolerance choices from drifting into per-test magic numbers nobody can justify.

### Why an "almost" comparison exists at all Binary floating point cannot represent most decimal fractions exactly, so arithmetic that is exact on paper is not exact in `float`: `0.1 + 0.2` evaluates to `0.30000000000000004`, and `assertEqual(0.1 + 0.2, 0.3)` fails. Any test over computed floats — an average, a rate, a running total, a duration — needs a tolerance rather than equality. ### What `assertAlmostEqual` actually does `TestCase.assertAlmostEqual(first, second)` short-circuits to success if the two values compare equal outright, and otherwise checks `round(first - second, 7) == 0`. That is an **absolute** tolerance of roughly `5e-8`, expressed as a number of decimal places. The knobs: * **`places=N`** — round the difference to `N` decimal places instead of 7. Still absolute, still measured in decimal digits after the point. * **`delta=D`** — assert `abs(first - second) <= D` instead. An explicit absolute bound, in the units of the values. * Passing both raises `TypeError: specify delta or places not both`. They are alternative spellings of the same idea, not a floor and a ceiling. * `assertNotAlmostEqual` is the mirror image, with the same parameters. ### The trap: absolute tolerance does not scale Because the default is absolute, the assertion gets *stricter* as the magnitudes grow, which is the opposite of what a reader expects. `assertAlmostEqual(0.1 + 0.2, 0.3)` passes comfortably; `assertAlmostEqual(1_000_000.4, 1_000_000.6)` fails with `1000000.4 != 1000000.6 within 7 places (0.19999999995343387 difference)`. Worse, near `1e9` the spacing between representable doubles is about `1.19e-7` — one unit in the last place is already larger than the default tolerance, so at that magnitude `assertAlmostEqual` has silently become an exact-equality assertion, and a result that differs by a single rounding step fails. This is why `delta=` is the right answer whenever the quantity has a meaningful scale and a meaningful error budget: `delta=0.5` for a byte count in megabytes, `delta=0.01` for money in units of one cent. `delta=` reads as a domain statement — "within half a unit" — where `places=7` reads as a number nobody chose. ### When you actually want a relative tolerance `unittest` has no relative-tolerance assertion. `math.isclose(a, b, rel_tol=1e-9)` does exactly that (and takes `abs_tol=` for the near-zero case, where relative tolerance degenerates). Combine it with an assertion that still produces a usable message: ```python self.assertTrue( math.isclose(measured, expected, rel_tol=1e-6), msg=f"measured={measured!r} expected={expected!r}", ) ``` The `msg=` is not optional politeness: `assertTrue` on its own reports `False is not true` and throws both numbers away. ### Edge cases worth knowing * **Infinity** passes through the equality short-circuit, so `assertAlmostEqual(inf, inf)` succeeds. * **NaN fails against itself**: `nan == nan` is `False` and `round(nan - nan, 7)` is `nan`, so the assertion reports `nan != nan within 7 places (nan difference)`. Assert NaN with `assertTrue(math.isnan(x))`. * **It is not float-only.** The `delta=` form needs only subtraction, `abs()` and `<=`, so it works on `datetime.datetime` values with a `datetime.timedelta` delta — a clean way to assert "this timestamp is within a second of now" without freezing the clock. * **It does not recurse into containers.** Two lists of floats are compared as lists, i.e. with `==`, so `assertAlmostEqual([0.1 + 0.2], [0.3])` fails. Compare element by element. * **`places` is decimal places, not significant figures.** `places=2` means "within about 0.005", regardless of whether the values are `0.5` or `500000.5`. ### Sometimes the right answer is not to use floats A tolerance is the correct tool for measured or accumulated quantities: durations, rates, averages, anything that went through repeated arithmetic. It is the wrong tool for values that were never meant to be approximate. Money is the standard example: if the production code stores amounts as `float`, `assertAlmostEqual(total, 19.99, places=2)` papers over a representation problem that will eventually surface as a cent that vanished. `decimal.Decimal` gives exact decimal arithmetic with an explicit rounding policy, and `fractions.Fraction` gives exact rational arithmetic; with either, the test goes back to a plain `assertEqual` and states an exact expectation. So the choice is really three-way, and the assertion you pick documents which one you believe: exact equality on an exact type, an absolute `delta=` when the quantity has a natural error budget, or a relative `math.isclose` when the values span orders of magnitude. Reaching for `assertAlmostEqual` with its default tolerance is the option that documents nothing.

  • Why does assertAlmostEqual succeed for 0.1 + 0.2 against 0.3 but fail for two values near a million?
    The tolerance is absolute, not relative: it is always about `5e-8` regardless of magnitude. A difference of `4e-17` near `0.3` is far inside it; a difference of `0.2` near `1e6` is far outside. Worse, near `1e9` a single unit in the last place of a double is about `1.19e-7`, already larger than the default tolerance, so the assertion degenerates into exact equality.
  • How do you assert a relative tolerance in a unittest test?
    There is no assertion for it; use `math.isclose(a, b, rel_tol=1e-9)` — with `abs_tol=` for values near zero, where relative tolerance degenerates — inside `assertTrue`. Always pass `msg=` with both values, because `assertTrue` on its own reports `False is not true` and discards the numbers you needed to see.
  • Does assertAlmostEqual work on anything other than floats?
    The `delta=` form needs only subtraction, `abs()` and `<=`, so it works on `datetime.datetime` values with a `datetime.timedelta` delta — a neat way to assert a timestamp is within a second of an expected instant. It does not recurse into containers: two lists of floats are compared with plain list equality, so you must compare element by element.
  • What happens when you compare NaN with assertAlmostEqual?
    It fails. `nan == nan` is `False`, so the equality short-circuit does not fire, and `round(nan - nan, 7)` is `nan`, which is not zero — the message reads `nan != nan within 7 places (nan difference)`. Assert NaN explicitly with `assertTrue(math.isnan(x))`. Infinity, by contrast, passes through the equality short-circuit.

places= is a ruler marked in fixed millimetres; when you start measuring kilometres you want delta=, a tolerance stated in the units you actually care about.

saying these in an interview costs you the question

  • Describes the default as a relative tolerance
  • Thinks places means significant figures
  • Says places and delta can be combined
  • Uses assertEqual on computed float values
  • Assumes the tolerance grows with the magnitude
  • Expects it to compare two lists of floats element-wise

context