Skip to main content

Lib/fractions.py

Source:

cpython 3.14 @ ab2d84fe1023/Lib/fractions.py

Map

LinesSymbolPurpose
1–40imports, __all__math, numbers, operator, re
41–80_RATIONAL_FORMAT regexparses "3/4", "1.5", "-7" strings
81–210Fraction.__new__normalization, gcd reduction, sign canonicalization
211–260Fraction._from_floatfloat.as_integer_ratio() path
261–305Fraction._from_decimalDecimal.as_integer_ratio() path
306–450arithmetic operatorscross-multiply with gcd reduction
451–535comparison operatorscross-multiply for exact ordering
536–630limit_denominatorStern-Brocot / continued-fraction walk
631–750__repr__, __str__, __format__, __hash__, __float__presentation and protocol

Reading

Construction and normalization

Fraction.__new__ is the only constructor. It accepts an integer pair, a string, a float, a Decimal, or another Rational. After any coercion the final step always reduces by math.gcd and forces the denominator positive. The sign of a negative fraction is carried by the numerator.

# CPython: Lib/fractions.py:107 Fraction.__new__
def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
...
if _normalize:
g = math.gcd(numerator, denominator)
if denominator < 0:
g = -g
numerator //= g
denominator //= g

The _normalize=False path is used internally when the caller already guarantees the pair is reduced, avoiding a redundant gcd call.

Float and Decimal conversion paths

Both conversions rely on a single protocol method rather than re-implementing IEEE 754 or decimal parsing.

# CPython: Lib/fractions.py:208 Fraction._from_float
@classmethod
def _from_float(cls, f):
if isinstance(f, numbers.Integral):
return cls(int(f))
elif not isinstance(f, float):
raise TypeError(...)
return cls(*f.as_integer_ratio())
# CPython: Lib/fractions.py:224 Fraction._from_decimal
@classmethod
def _from_decimal(cls, dec):
...
return cls(*dec.as_integer_ratio())

float.as_integer_ratio() returns an exact (numerator, denominator) pair where the denominator is a power of two, so no precision is lost. __float__ goes the other direction by dividing numerator by denominator using true division.

Arithmetic via cross-multiplication and gcd reduction

To add a/b + c/d, the file computes (a*d + b*c) / (b*d) then reduces. The intermediate products can be large, but Python int is unbounded, so this is exact.

# CPython: Lib/fractions.py:340 Fraction.__add__
def _add(a, b):
da, db = a.denominator, b.denominator
return Fraction(
a.numerator * db + b.numerator * da,
da * db,
_normalize=True,
)
__add__ = _operator_fallbacks(_add, operator.add)

_operator_fallbacks wraps the core function to produce both __add__ and __radd__, handling type dispatch so that Fraction + int, int + Fraction, and Fraction + Fraction all work correctly. Multiplication follows the same pattern: (a*c) / (b*d) with a gcd pass after.

limit_denominator and the Stern-Brocot tree

Given a maximum denominator, this method finds the closest rational to self whose denominator does not exceed the limit. It uses the continued-fraction convergent algorithm, which is equivalent to walking the Stern-Brocot tree.

# CPython: Lib/fractions.py:553 Fraction.limit_denominator
def limit_denominator(self, max_denominator=10**6):
if max_denominator < 1:
raise ValueError(...)
if self.denominator <= max_denominator:
return Fraction(self)
p0, q0, p1, q1 = 0, 1, 1, 0
n, d = self.numerator, self.denominator
while True:
a = n // d
q2 = q0 + a * q1
if q2 > max_denominator:
break
p0, q0, p1, q1 = p1, q1, p0 + a * p1, q2
n, d = d, n - a * d
...

The loop extracts continued-fraction coefficients one at a time. When the next convergent would exceed max_denominator, it picks the better of the last two candidates by comparing absolute distances.

gopy notes

Status: not yet ported.

Planned package path: module/fractions/.

Key porting notes:

  • Fraction stores _numerator and _denominator as plain attributes set via object.__setattr__ to work around the slot-less base class. In Go, struct fields are fine.
  • _operator_fallbacks is a higher-order function that manufactures __add__/__radd__ pairs. The gopy equivalent is two separate method registrations on the type object.
  • math.gcd in CPython 3.9+ handles negative inputs correctly and returns a non-negative result, so the sign fix on the denominator is done before calling it. Match that order exactly.
  • __hash__ must satisfy hash(Fraction(n)) == hash(n) for any integer n. The numeric hash protocol from numbers.py governs the formula.