|
4 | 4 | """Fraction, infinite-precision, rational numbers."""
|
5 | 5 |
|
6 | 6 | from decimal import Decimal
|
| 7 | +import functools |
7 | 8 | import math
|
8 | 9 | import numbers
|
9 | 10 | import operator
|
|
20 | 21 | # _PyHASH_MODULUS.
|
21 | 22 | _PyHASH_INF = sys.hash_info.inf
|
22 | 23 |
|
| 24 | +@functools.lru_cache(maxsize = 1 << 14) |
| 25 | +def _hash_algorithm(numerator, denominator): |
| 26 | + |
| 27 | + # To make sure that the hash of a Fraction agrees with the hash |
| 28 | + # of a numerically equal integer, float or Decimal instance, we |
| 29 | + # follow the rules for numeric hashes outlined in the |
| 30 | + # documentation. (See library docs, 'Built-in Types'). |
| 31 | + |
| 32 | + try: |
| 33 | + dinv = pow(denominator, -1, _PyHASH_MODULUS) |
| 34 | + except ValueError: |
| 35 | + # ValueError means there is no modular inverse. |
| 36 | + hash_ = _PyHASH_INF |
| 37 | + else: |
| 38 | + # The general algorithm now specifies that the absolute value of |
| 39 | + # the hash is |
| 40 | + # (|N| * dinv) % P |
| 41 | + # where N is self._numerator and P is _PyHASH_MODULUS. That's |
| 42 | + # optimized here in two ways: first, for a non-negative int i, |
| 43 | + # hash(i) == i % P, but the int hash implementation doesn't need |
| 44 | + # to divide, and is faster than doing % P explicitly. So we do |
| 45 | + # hash(|N| * dinv) |
| 46 | + # instead. Second, N is unbounded, so its product with dinv may |
| 47 | + # be arbitrarily expensive to compute. The final answer is the |
| 48 | + # same if we use the bounded |N| % P instead, which can again |
| 49 | + # be done with an int hash() call. If 0 <= i < P, hash(i) == i, |
| 50 | + # so this nested hash() call wastes a bit of time making a |
| 51 | + # redundant copy when |N| < P, but can save an arbitrarily large |
| 52 | + # amount of computation for large |N|. |
| 53 | + hash_ = hash(hash(abs(numerator)) * dinv) |
| 54 | + result = hash_ if numerator >= 0 else -hash_ |
| 55 | + return -2 if result == -1 else result |
| 56 | + |
23 | 57 | _RATIONAL_FORMAT = re.compile(r"""
|
24 | 58 | \A\s* # optional whitespace at the start,
|
25 | 59 | (?P<sign>[-+]?) # an optional sign, then
|
@@ -646,36 +680,7 @@ def __round__(self, ndigits=None):
|
646 | 680 |
|
647 | 681 | def __hash__(self):
|
648 | 682 | """hash(self)"""
|
649 |
| - |
650 |
| - # To make sure that the hash of a Fraction agrees with the hash |
651 |
| - # of a numerically equal integer, float or Decimal instance, we |
652 |
| - # follow the rules for numeric hashes outlined in the |
653 |
| - # documentation. (See library docs, 'Built-in Types'). |
654 |
| - |
655 |
| - try: |
656 |
| - dinv = pow(self._denominator, -1, _PyHASH_MODULUS) |
657 |
| - except ValueError: |
658 |
| - # ValueError means there is no modular inverse. |
659 |
| - hash_ = _PyHASH_INF |
660 |
| - else: |
661 |
| - # The general algorithm now specifies that the absolute value of |
662 |
| - # the hash is |
663 |
| - # (|N| * dinv) % P |
664 |
| - # where N is self._numerator and P is _PyHASH_MODULUS. That's |
665 |
| - # optimized here in two ways: first, for a non-negative int i, |
666 |
| - # hash(i) == i % P, but the int hash implementation doesn't need |
667 |
| - # to divide, and is faster than doing % P explicitly. So we do |
668 |
| - # hash(|N| * dinv) |
669 |
| - # instead. Second, N is unbounded, so its product with dinv may |
670 |
| - # be arbitrarily expensive to compute. The final answer is the |
671 |
| - # same if we use the bounded |N| % P instead, which can again |
672 |
| - # be done with an int hash() call. If 0 <= i < P, hash(i) == i, |
673 |
| - # so this nested hash() call wastes a bit of time making a |
674 |
| - # redundant copy when |N| < P, but can save an arbitrarily large |
675 |
| - # amount of computation for large |N|. |
676 |
| - hash_ = hash(hash(abs(self._numerator)) * dinv) |
677 |
| - result = hash_ if self._numerator >= 0 else -hash_ |
678 |
| - return -2 if result == -1 else result |
| 683 | + return _hash_algorithm(self._numerator, self._denominator) |
679 | 684 |
|
680 | 685 | def __eq__(a, b):
|
681 | 686 | """a == b"""
|
|
0 commit comments