Source code for joythief.numbers

"""Matchers for `numeric types`_ (e.g. :py:class:`float`).

.. _numeric types: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex

"""

import math
import typing as tp

from joythief.core import Matcher


[docs] class CloseTo(Matcher[float]): """Matches any numeric value approximately equal to an expected value. Comparison is delegated to :py:func:`math.isclose`, so the same `tolerance semantics`_ apply: the relative tolerance is the maximum allowed difference relative to the larger absolute value of the inputs, and the absolute tolerance is the minimum allowed difference - useful when comparing values near zero. :param expected: the value to compare against. :param rel_tol: relative tolerance, defaulting to ``1e-09``. Must be less than ``1.0``. :param abs_tol: absolute tolerance, defaulting to ``0.0``. :raises ValueError: if either tolerance is negative, or ``rel_tol`` is not less than ``1.0``. .. _tolerance semantics: https://docs.python.org/3/library/math.html#math.isclose """ _expected: float _rel_tol: tp.Optional[float] _abs_tol: tp.Optional[float] def __init__( self, expected: float, *, rel_tol: tp.Optional[float] = None, abs_tol: tp.Optional[float] = None, ): super().__init__() if (rel_tol is not None and rel_tol < 0) or ( abs_tol is not None and abs_tol < 0 ): raise ValueError("tolerances must be non-negative") if rel_tol is not None and rel_tol >= 1.0: raise ValueError("rel_tol must be less than 1.0") self._expected = expected self._rel_tol = rel_tol self._abs_tol = abs_tol def compare(self, other: tp.Any) -> bool: if not isinstance(other, (int, float)): return self.not_implemented return math.isclose( other, self._expected, rel_tol=1e-09 if self._rel_tol is None else self._rel_tol, abs_tol=0.0 if self._abs_tol is None else self._abs_tol, ) def represent(self) -> str: parameters = [repr(self._expected)] if self._rel_tol is not None: parameters.append(f"rel_tol={self._rel_tol!r}") if self._abs_tol is not None: parameters.append(f"abs_tol={self._abs_tol!r}") return f"CloseTo({', '.join(parameters)})"
[docs] class NaN(Matcher[float]): """Matches any :py:class:`float` instance representing NaN. `IEEE 754`_ NaN (*"not a number"*) instances are, by definition, not equal to each other. This matcher compares equal to e.g. :py:data:`math.nan` or :code:`float("nan")` using :py:func:`math.isnan`. Originally formulated for `this answer`_. .. _IEEE 754: https://en.wikipedia.org/wiki/IEEE_754 .. _this answer: https://stackoverflow.com/a/79699116/3001761 """ def compare(self, other: tp.Any) -> bool: if not isinstance(other, float): return self.not_implemented return math.isnan(other) def represent(self) -> str: return super().represent()