03
Rational numbers as ratios¶
A pair of integers (p, q), with q ≠ 0, represents
the ratio p/q. Cross multiplication defines equality.
Integers are equivalence classes of natural-number pairs. Their operations are available before we use them as numerators and denominators.
- derive rational equality and addition from integer operations;
- identify the invariant enforced by
__post_init__; - explain why reduction is explicit rather than automatic;
- explain equality and hashing across equivalent representatives.
Definition
For nonzero denominators:
Addition and multiplication are:
These formulas use only operations already constructed for integers.
Representation
@dataclass(frozen=True, slots=True, eq=False, repr=False)
class Rational:
p: Integer
q: Integer
def __post_init__(self) -> None:
if not isinstance(self.p, Integer) or not isinstance(self.q, Integer):
raise TypeError("Rational.p and Rational.q must be Integer values")
if self.q == Z_ZERO:
raise ZeroDivisionError("the denominator cannot be zero")
The generated initializer stores the fields, then __post_init__ rejects
values outside the representation. It does not reduce 2/4 to 1/2; both
representatives remain available for study.
Implementation
@log(log_level=21)
def __eq__(self, other: object) -> tuple[bool, LogMessage]:
converted = _coerce_rational(other)
if converted is None:
return cast(bool, NotImplemented), lambda: "NotImplemented"
result = self.p * converted.q == self.q * converted.p
return result, lambda: (
f"{self!r} == {converted!r} ⇔ "
f"{self.p!r} * {converted.q!r} == "
f"{self.q!r} * {converted.p!r}"
)
Again, one line is the mathematical definition with fields substituted. The
addition method applies the formula above and calls reduction() on the
result. reduction() makes the denominator positive and divides numerator and
denominator by their greatest common divisor.
Hashing also uses a reduced canonical pair. Therefore 1/2 and 2/4 compare
equal and have equal hashes, as Python requires for dictionary keys and sets.
Trace
What two integer products does 1/2 == 2/4 compare? Why can the
result be true even though the stored fields differ?
Read the cross-product trace before the printed booleans.
Test
Tests check zero-denominator rejection, equivalent representatives, arithmetic, ordering with negative denominators, reduction, and the equality/hash contract. They protect implementation behavior for concrete cases, while the formulas provide the general mathematical reasoning.
Boundary
Rationals contain every integer but still have gaps: there is no rational whose square is 2. The next chapters keep rational arithmetic and use a polynomial plus an interval to identify such a real number.