We do need to define the __hash__() method properly. See section 4.4.4 of the Python Standard Library for information on computing hash values for numeric types. That section defines a hash_fraction() function, which is the recommended solution for what we're doing here. Our method looks like this:
def __hash__(self) -> int:
P = sys.hash_info.modulus
m, n = self.value, self.scale
# Remove common factors of P. (Unnecessary if m and n already coprime.)
while m % P == n % P == 0:
m, n = m // P, n // P
if n % P == 0:
hash_ = sys.hash_info.inf
else:
# Fermat's Little Theorem: pow(n, P-1, P) is 1, so
# pow(n, P-2, P) gives the inverse of n modulo P.
hash_ = (abs(m) % P) * pow(n, P - 2, P) % P
if m < 0:
hash_ = -hash_
if hash_ == -1:
hash_ = -2
return hash_
This reduces...