summaryrefslogtreecommitdiffstats
path: root/Lib/fractions.py
diff options
context:
space:
mode:
authorRaymond Hettinger <rhettinger@users.noreply.github.com>2022-09-07 15:31:50 (GMT)
committerGitHub <noreply@github.com>2022-09-07 15:31:50 (GMT)
commit3eaf70d8369a7d78f3e21949e438c8ff8a30f433 (patch)
treeaee349f1d95abe0e668488c0360e669089ca4a2b /Lib/fractions.py
parent0cd992c0005a4e605fe5b588e28bf9c0468d02e7 (diff)
downloadcpython-3eaf70d8369a7d78f3e21949e438c8ff8a30f433.zip
cpython-3eaf70d8369a7d78f3e21949e438c8ff8a30f433.tar.gz
cpython-3eaf70d8369a7d78f3e21949e438c8ff8a30f433.tar.bz2
GH-96465: Cache hashes for Fraction instances (GH-96483)
Diffstat (limited to 'Lib/fractions.py')
-rw-r--r--Lib/fractions.py65
1 files changed, 35 insertions, 30 deletions
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 43b72ae..75c7df1 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -4,6 +4,7 @@
"""Fraction, infinite-precision, rational numbers."""
from decimal import Decimal
+import functools
import math
import numbers
import operator
@@ -20,6 +21,39 @@ _PyHASH_MODULUS = sys.hash_info.modulus
# _PyHASH_MODULUS.
_PyHASH_INF = sys.hash_info.inf
+@functools.lru_cache(maxsize = 1 << 14)
+def _hash_algorithm(numerator, denominator):
+
+ # To make sure that the hash of a Fraction agrees with the hash
+ # of a numerically equal integer, float or Decimal instance, we
+ # follow the rules for numeric hashes outlined in the
+ # documentation. (See library docs, 'Built-in Types').
+
+ try:
+ dinv = pow(denominator, -1, _PyHASH_MODULUS)
+ except ValueError:
+ # ValueError means there is no modular inverse.
+ hash_ = _PyHASH_INF
+ else:
+ # The general algorithm now specifies that the absolute value of
+ # the hash is
+ # (|N| * dinv) % P
+ # where N is self._numerator and P is _PyHASH_MODULUS. That's
+ # optimized here in two ways: first, for a non-negative int i,
+ # hash(i) == i % P, but the int hash implementation doesn't need
+ # to divide, and is faster than doing % P explicitly. So we do
+ # hash(|N| * dinv)
+ # instead. Second, N is unbounded, so its product with dinv may
+ # be arbitrarily expensive to compute. The final answer is the
+ # same if we use the bounded |N| % P instead, which can again
+ # be done with an int hash() call. If 0 <= i < P, hash(i) == i,
+ # so this nested hash() call wastes a bit of time making a
+ # redundant copy when |N| < P, but can save an arbitrarily large
+ # amount of computation for large |N|.
+ hash_ = hash(hash(abs(numerator)) * dinv)
+ result = hash_ if numerator >= 0 else -hash_
+ return -2 if result == -1 else result
+
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start,
(?P<sign>[-+]?) # an optional sign, then
@@ -646,36 +680,7 @@ class Fraction(numbers.Rational):
def __hash__(self):
"""hash(self)"""
-
- # To make sure that the hash of a Fraction agrees with the hash
- # of a numerically equal integer, float or Decimal instance, we
- # follow the rules for numeric hashes outlined in the
- # documentation. (See library docs, 'Built-in Types').
-
- try:
- dinv = pow(self._denominator, -1, _PyHASH_MODULUS)
- except ValueError:
- # ValueError means there is no modular inverse.
- hash_ = _PyHASH_INF
- else:
- # The general algorithm now specifies that the absolute value of
- # the hash is
- # (|N| * dinv) % P
- # where N is self._numerator and P is _PyHASH_MODULUS. That's
- # optimized here in two ways: first, for a non-negative int i,
- # hash(i) == i % P, but the int hash implementation doesn't need
- # to divide, and is faster than doing % P explicitly. So we do
- # hash(|N| * dinv)
- # instead. Second, N is unbounded, so its product with dinv may
- # be arbitrarily expensive to compute. The final answer is the
- # same if we use the bounded |N| % P instead, which can again
- # be done with an int hash() call. If 0 <= i < P, hash(i) == i,
- # so this nested hash() call wastes a bit of time making a
- # redundant copy when |N| < P, but can save an arbitrarily large
- # amount of computation for large |N|.
- hash_ = hash(hash(abs(self._numerator)) * dinv)
- result = hash_ if self._numerator >= 0 else -hash_
- return -2 if result == -1 else result
+ return _hash_algorithm(self._numerator, self._denominator)
def __eq__(a, b):
"""a == b"""