pytest-dev/pytest · error · ValueError

absolute tolerance can't be NaN.

Error message

absolute tolerance can't be NaN.

What it means

The ApproxScalar.tolerance property rejects a NaN absolute tolerance because comparisons against NaN always evaluate to False, which would make every assertion silently fail in a confusing way. pytest raises ValueError so the user sees the bad input immediately. The check uses math.isnan and runs after the negativity check.

Solutions

  1. Pass a finite positive tolerance: `approx(1.0, abs=1e-3)`.
  2. Sanitize loaded tolerances: `if math.isnan(t): raise ValueError(...)` or `t = t if not math.isnan(t) else DEFAULT`.
  3. Fix upstream computation so the denominator cannot be zero.

Example fix

# before
approx(value, abs=float('nan'))
# after
approx(value, abs=1e-3)
Defensive patterns

Strategy: validation

Validate before calling

import math
def is_finite_tolerance(t) -> bool:
    return t is None or (isinstance(t, (int, float)) and not math.isnan(t))
# usage
if not is_finite_tolerance(abs_tol):
    raise ValueError(f'absolute tolerance must be finite, got {abs_tol}')
assert actual == approx(expected, abs=abs_tol)

Type guard

import math
def is_finite_scalar(v) -> bool:
    return isinstance(v, (int, float)) and not math.isnan(v)

Prevention

When it happens

Trigger: Calling approx(1.0, abs=float('nan')); abs loaded from a JSON/CSV field that contained 'nan' or non-numeric data parsed to NaN; arithmetic that produces NaN (0.0/0.0). Triggered at ApproxScalar.tolerance (src/_pytest/approx.py:564-565).

Common situations: Parsing tolerances from data files where missing values become NaN; tolerance computed as a ratio whose denominator is zero; cross-platform float parsing differences.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/8d873abd7dd39eb0. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/approx.py:565

    @property
    def tolerance(self):
        """Return the tolerance for the comparison.

        This could be either an absolute tolerance or a relative tolerance,
        depending on what the user specified or which would be larger.
        """
        # Figure out what the absolute tolerance should be.  ``self.abs`` is
        # either None or a value specified by the user.
        absolute_tolerance = (
            self.abs if self.abs is not None else self.DEFAULT_ABSOLUTE_TOLERANCE
        )

        if absolute_tolerance < 0:
            raise ValueError(
                f"absolute tolerance can't be negative: {absolute_tolerance}"
            )
        if math.isnan(absolute_tolerance):
            raise ValueError("absolute tolerance can't be NaN.")

        # If the user specified an absolute tolerance but not a relative one,
        # just return the absolute tolerance.
        if self.rel is None:
            if self.abs is not None:
                return absolute_tolerance

        # Figure out what the relative tolerance should be.  ``self.rel`` is
        # either None or a value specified by the user.  This is done after
        # we've made sure the user didn't ask for an absolute tolerance only,
        # because we don't want to raise errors about the relative tolerance if
        # we aren't even going to use it.
        rel = self.rel if self.rel is not None else self.DEFAULT_RELATIVE_TOLERANCE
        # expected is SupportAbs, checked in __init__.
        # The typing here is not exact...
        abs_expected: ExpectedT = abs(self.expected)  # type: ignore[arg-type]
        relative_tolerance: float | Decimal = rel * abs_expected  # type: ignore[operator]

View on GitHub (pinned to 0d6fbdeffa)