Perform approximate comparisons where the expected value is a single number.
| 392 | |
| 393 | |
| 394 | class ApproxScalar(ApproxBase): |
| 395 | """Perform approximate comparisons where the expected value is a single number.""" |
| 396 | |
| 397 | # Using Real should be better than this Union, but not possible yet: |
| 398 | # https://github.com/python/typeshed/pull/3108 |
| 399 | DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 |
| 400 | DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 |
| 401 | |
| 402 | def __repr__(self) -> str: |
| 403 | """Return a string communicating both the expected value and the |
| 404 | tolerance for the comparison being made. |
| 405 | |
| 406 | For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. |
| 407 | """ |
| 408 | # Don't show a tolerance for values that aren't compared using |
| 409 | # tolerances, i.e. non-numerics and infinities. Need to call abs to |
| 410 | # handle complex numbers, e.g. (inf + 1j). |
| 411 | if ( |
| 412 | isinstance(self.expected, bool) |
| 413 | or (not isinstance(self.expected, Complex | Decimal)) |
| 414 | or math.isinf(abs(self.expected) or isinstance(self.expected, bool)) |
| 415 | ): |
| 416 | return str(self.expected) |
| 417 | |
| 418 | # If a sensible tolerance can't be calculated, self.tolerance will |
| 419 | # raise a ValueError. In this case, display '???'. |
| 420 | try: |
| 421 | if 1e-3 <= self.tolerance < 1e3: |
| 422 | vetted_tolerance = f"{self.tolerance:n}" |
| 423 | else: |
| 424 | vetted_tolerance = f"{self.tolerance:.1e}" |
| 425 | |
| 426 | if ( |
| 427 | isinstance(self.expected, Complex) |
| 428 | and self.expected.imag |
| 429 | and not math.isinf(self.tolerance) |
| 430 | ): |
| 431 | vetted_tolerance += " ∠ ±180°" |
| 432 | except ValueError: |
| 433 | vetted_tolerance = "???" |
| 434 | |
| 435 | return f"{self.expected} ± {vetted_tolerance}" |
| 436 | |
| 437 | def __eq__(self, actual) -> bool: |
| 438 | """Return whether the given value is equal to the expected value |
| 439 | within the pre-specified tolerance.""" |
| 440 | |
| 441 | def is_bool(val: Any) -> bool: |
| 442 | # Check if `val` is a native bool or numpy bool. |
| 443 | if isinstance(val, bool): |
| 444 | return True |
| 445 | if np := sys.modules.get("numpy"): |
| 446 | return isinstance(val, np.bool_) |
| 447 | return False |
| 448 | |
| 449 | asarray = _as_numpy_array(actual) |
| 450 | if asarray is not None: |
| 451 | # Call ``__eq__()`` manually to prevent infinite-recursion with |