Perform approximate comparisons where the expected value is a single number.
| 380 | |
| 381 | |
| 382 | class ApproxScalar(ApproxBase): |
| 383 | """Perform approximate comparisons where the expected value is a single number.""" |
| 384 | |
| 385 | # Using Real should be better than this Union, but not possible yet: |
| 386 | # https://github.com/python/typeshed/pull/3108 |
| 387 | DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 |
| 388 | DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 |
| 389 | |
| 390 | def __repr__(self) -> str: |
| 391 | """Return a string communicating both the expected value and the |
| 392 | tolerance for the comparison being made. |
| 393 | |
| 394 | For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. |
| 395 | """ |
| 396 | # Don't show a tolerance for values that aren't compared using |
| 397 | # tolerances, i.e. non-numerics and infinities. Need to call abs to |
| 398 | # handle complex numbers, e.g. (inf + 1j). |
| 399 | if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( |
| 400 | abs(self.expected) # type: ignore[arg-type] |
| 401 | ): |
| 402 | return str(self.expected) |
| 403 | |
| 404 | # If a sensible tolerance can't be calculated, self.tolerance will |
| 405 | # raise a ValueError. In this case, display '???'. |
| 406 | try: |
| 407 | vetted_tolerance = f"{self.tolerance:.1e}" |
| 408 | if ( |
| 409 | isinstance(self.expected, Complex) |
| 410 | and self.expected.imag |
| 411 | and not math.isinf(self.tolerance) |
| 412 | ): |
| 413 | vetted_tolerance += " ∠ ±180°" |
| 414 | except ValueError: |
| 415 | vetted_tolerance = "???" |
| 416 | |
| 417 | return f"{self.expected} ± {vetted_tolerance}" |
| 418 | |
| 419 | def __eq__(self, actual) -> bool: |
| 420 | """Return whether the given value is equal to the expected value |
| 421 | within the pre-specified tolerance.""" |
| 422 | asarray = _as_numpy_array(actual) |
| 423 | if asarray is not None: |
| 424 | # Call ``__eq__()`` manually to prevent infinite-recursion with |
| 425 | # numpy<1.13. See #3748. |
| 426 | return all(self.__eq__(a) for a in asarray.flat) |
| 427 | |
| 428 | # Short-circuit exact equality. |
| 429 | if actual == self.expected: |
| 430 | return True |
| 431 | |
| 432 | # If either type is non-numeric, fall back to strict equality. |
| 433 | # NB: we need Complex, rather than just Number, to ensure that __abs__, |
| 434 | # __sub__, and __float__ are defined. |
| 435 | if not ( |
| 436 | isinstance(self.expected, (Complex, Decimal)) |
| 437 | and isinstance(actual, (Complex, Decimal)) |
| 438 | ): |
| 439 | return False |