skip to content

How do you choose math.isclose rel_tol and abs_tol when comparing drifted totals?

level: seniorimportance: should knowfreq 40%

answer

  1. Two tolerances, combined with max
  2. One scales with magnitude, one does not
  3. Comparing against exactly zero needs the flat one
  4. Defaults are 1e-09 and 0.0
  5. Both are keyword-only arguments

basics

~20 s

math.isclose passes when the difference is at most max(rel_tol * max(abs(a), abs(b)), abs_tol). Size rel_tol from how many operations produced the value, and set abs_tol from the smallest difference that matters, because the default 0.0 makes any comparison against zero fail.

solid answer

~40 s

`math.isclose(a, b)` returns True when `abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)`. Both tolerances are keyword-only: `rel_tol` defaults to 1e-09 and scales with the magnitude of the inputs, so it is the right knob for accumulated arithmetic drift; `abs_tol` defaults to 0.0 and is a flat floor, so it is the only thing that can make a comparison against exactly 0.0 succeed. Choose them from the domain, not by taste: `rel_tol` from roughly how many roundings the value has been through, `abs_tol` from the smallest difference anyone would act on - half a unit for whole-unit inventory counts, for example. Remember the special values: nan is close to nothing, not even itself, and each infinity is close only to itself.

code

python · 10 lines
python
import math

# Two systems report the same total after different conversion paths.
a, b = 1_000_000.0, 1_000_000.0001
print(a == b)                                 # False
print(math.isclose(a, b))                     # True: slack is 1e-9 * 1e6

# Near zero the relative branch collapses to nothing.
print(math.isclose(1e-9, 0.0))                # False
print(math.isclose(1e-9, 0.0, abs_tol=1e-6))  # True

go deeper

for a junior

Know that math.isclose exists and that it is how you compare two computed floats. Being able to say that rel_tol is a percentage-style slack and abs_tol a flat allowance is plenty at this stage.

for a middle

Explain the formula and the max() between the two tolerances, and be able to demonstrate why a comparison against exactly 0.0 fails until abs_tol is set. Know that both tolerances are keyword-only.

for a senior

Justify the numbers from the data: the error a chain of operations can plausibly accumulate, and the smallest difference anyone would act on. Interviewers want to hear that widening a tolerance to silence a failure destroys a real integrity signal.

for a principal

Frame it as a policy question. Decide where in a multi-service pipeline comparisons happen, whether reconciliation should be approximate at all, and which quantities are moved to exact integer representation so that tolerance choices never have to be re-litigated per service.

## The exact rule `math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)` returns True when ```python abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) ``` Three details are worth reading off that line. It is **symmetric**, because it scales by the larger of the two magnitudes rather than by "the expected value" - swapping the arguments cannot change the verdict. The two tolerances are combined with `max`, so they are alternatives, not a sum: a pair passes if *either* the relative or the absolute test passes. And both are **keyword-only**, so `math.isclose(a, b, 0.01)` is a `TypeError`, not a silently different tolerance. ## What each tolerance is for `rel_tol` is a fraction of the numbers being compared. At the default 1e-09 you are allowing about a millionth of a percent of slack, which is generous next to the roughly 1.1e-16 relative error of a single float operation and tight enough to catch a real disagreement. Relative tolerance is what you want for values produced by arithmetic, because the absolute size of the error scales with the size of the numbers: an error of 0.001 is noise on a total of a million and catastrophic on a total of 0.002. `abs_tol` is a flat floor in the units of the quantity. Its default of 0.0 is the single most common trap in this API. Comparing a small value against exactly zero, the relative branch computes `rel_tol * max(abs(x), 0.0)`, which is a fraction of `x` and therefore always smaller than `x` itself. So `math.isclose(1e-300, 0.0)` is False, and every "is this effectively zero" check needs an explicit `abs_tol`. ## Choosing the numbers on real data Take an inventory sync where quantities move between two systems through a 17-service dependency graph, and each hop applies its own unit conversion in float. The two sides then report totals that differ in the last few digits - a pure floating-point rounding drift, not a lost record. Two questions set the tolerances. *How much drift can arithmetic alone explain?* Each rounding contributes at most about 1.1e-16 relative error, and a chain of a few dozen conversions and a summation over a few million rows keeps you many orders of magnitude below 1e-9. So the default `rel_tol` is already a loose bound: if the two totals differ by more than that, something other than rounding is wrong, which is exactly the alarm you want. Deliberately widening `rel_tol` to 1e-6 to "make the check pass" converts a data-integrity alarm into silence. *What difference would a human act on?* That sets `abs_tol`. For counts of physical units, a difference below half a unit cannot represent a real discrepancy, so `abs_tol=0.5` is defensible and also rescues the near-zero case where one side legitimately reports 0.0. Write the reasoning next to the call, because a bare `1e-6` in code is unreviewable. ## What isclose is not It is **not transitive**. `isclose(a, b)` and `isclose(b, c)` do not imply `isclose(a, c)`, so tolerance comparison cannot be used to group, dedupe or key values - there is no consistent equivalence class, and building a dict or a set on "close enough" produces order-dependent results. It is **not an exactness fix**. If the two totals are money or counts that must reconcile exactly, the answer is to change the representation - integer smallest units, or an exact base-ten decimal type - not to widen a tolerance until the mismatch stops being reported. It says nothing about **ordering**. `isclose` answers one question; if you need "a is meaningfully greater than b", write that comparison with the same tolerance explicitly. ## Special values The IEEE special values follow the standard rather than intuition. `math.isclose(float('nan'), float('nan'))` is False - nan is close to nothing, including itself - so a tolerance check on a pipeline that can produce nan silently reports "not close" instead of raising. Each infinity is close only to itself with the same sign, and `math.isclose(math.inf, -math.inf)` is False. Guard with `math.isnan()` or `math.isfinite()` before comparing if either side can be a special value. ## The interview point The interviewer is checking whether tolerances are chosen or copied. A strong answer states the formula, explains why `abs_tol` exists and why its 0.0 default breaks comparisons against zero, ties each number to a property of the data rather than to a habit, and knows when to stop tuning and change the representation instead.

  • Why does math.isclose(x, 0.0) return False for any tiny non-zero x?
    Because the relative branch scales by max(abs(x), 0.0), which is x itself, so the allowed slack is rel_tol * x - always smaller than the difference x. Zero has no magnitude to be relative to. The only tolerance that can pass is abs_tol, and its default is 0.0, so any near-zero check must set abs_tol explicitly from the smallest difference the domain cares about.
  • How does math.isclose treat nan and infinity?
    By IEEE 754 rules. nan is close to nothing at all, including another nan, so isclose returns False and a corrupted value passes silently as a mismatch rather than raising. Each infinity is close only to itself with the same sign, so isclose(inf, inf) is True and isclose(inf, -inf) is False. Screen inputs with math.isnan or math.isfinite when the source can produce them.
  • When would you stop tuning tolerances and change the representation instead?
    When the values must reconcile exactly rather than approximately - money, physical counts, anything an auditor compares. Tolerance comparison is the right tool for measured or derived quantities where some error is inherent. If the correct answer is a single exact number, move to integer smallest units or an exact base-ten decimal type; widening a tolerance until a mismatch disappears removes the alarm, not the defect.

saying these in an interview costs you the question

  • Leaves abs_tol at 0.0 while comparing against zero
  • Passes tolerances positionally and gets a TypeError
  • Picks a tolerance with no link to the data
  • Widens rel_tol until the failing check passes
  • Assumes math.isclose(nan, nan) returns True
  • Treats closeness as transitive when grouping values

context