fractions: limit_denominator() Breaks Ties Toward Floor (tested)

Fraction.limit_denominator(max_denominator) finds the closest fraction to a value with a denominator no larger than the given limit -- a common way to round a Fraction to a coarser precision. We measured what it does for exact halfway values, where "closest" is ambiguous, and compared it to Python's built-in round(), which everyone already knows rounds halves to even.

1. round() breaks ties to the nearest even number -- well known, shown for reference

print(round(0.5), round(1.5), round(2.5), round(3.5))
0 2 2 4

Measured: Python's built-in round() uses round-half-to-even (banker's rounding): 0.5 rounds to 0, but 1.5 rounds up to 2 (the nearer even number), and 2.5 rounds down to 2 for the same reason. This is documented and widely known.

2. Fraction.limit_denominator() does something different: it always breaks ties toward floor

from fractions import Fraction

for n, d in [(1, 2), (3, 2), (5, 2), (7, 2)]:
    f = Fraction(n, d)
    print(f, "-> limit_denominator(1):", f.limit_denominator(1))
1/2 -> limit_denominator(1): 0
3/2 -> limit_denominator(1): 1
5/2 -> limit_denominator(1): 2
7/2 -> limit_denominator(1): 3

Measured: rounding to the nearest whole number via limit_denominator(1) gives 1/2 -> 0, 3/2 -> 1, 5/2 -> 2, 7/2 -> 3 -- every exact halfway case rounds down, regardless of whether the result lands on an even or odd number. round(1.5) gives 2 (nearest even); Fraction(3,2).limit_denominator(1) gives 1 instead. These two "round to nearest whole number" operations disagree on every single tie.

3. For negative values, "toward floor" means away from zero, not toward it

from fractions import Fraction

for n, d in [(-1, 2), (-3, 2), (-5, 2)]:
    f = Fraction(n, d)
    print(f, "-> limit_denominator(1):", f.limit_denominator(1))
-1/2 -> limit_denominator(1): -1
-3/2 -> limit_denominator(1): -2
-5/2 -> limit_denominator(1): -3

Measured: -1/2 rounds to -1, not 0 -- confirming the tie-break is toward floor (more negative), not toward zero or toward even. This matches math.floor() on the equivalent value exactly (math.floor(-0.5) == -1), and is the opposite of what round-half-up or round-half-even would give for a negative halfway value.

If code switched from round() on floats to Fraction.limit_denominator() for exact decimal-safe rounding (a reasonable-looking migration), every value that happens to land on an exact tie after the conversion will silently round differently -- current code review or tests built around round()'s round-half-even expectations won't catch it, because non-tie values round identically either way. Don't assume limit_denominator() matches round()'s tie-breaking; if a specific rounding rule matters, round explicitly (e.g. with decimal.Decimal.quantize() and an explicit rounding mode) instead of relying on limit_denominator()'s undocumented tie behavior.

Frequently asked questions

Does Fraction.limit_denominator() round halfway values the same as round()?

No -- we measured limit_denominator(1) breaking every exact .5 tie toward floor (1.5 -> 1, 2.5 -> 2), while Python's round() breaks ties to the nearest even number (1.5 -> 2, 2.5 -> 2). They agree on 2.5 by coincidence but disagree on 1.5, 3.5, and every other odd-rounding tie.

How does Fraction.limit_denominator() handle an exact halfway value?

We measured it consistently rounding toward floor (more negative, or toward zero only for positive ties) -- 1/2 -> 0, 3/2 -> 1, 5/2 -> 2, and -1/2 -> -1, -3/2 -> -2. This matches math.floor() on the equivalent value in every case we tested, not round-half-even or round-half-up.

Why did switching from round() to Fraction.limit_denominator() change my rounding results?

Because the two use different tie-breaking rules for exact halfway values -- we confirmed limit_denominator() rounds ties toward floor while round() rounds ties to the nearest even number. Only values that land on an exact tie after the switch are affected; everything else rounds the same.

Does Fraction.limit_denominator() round negative ties toward zero?

No -- we measured Fraction(-1, 2).limit_denominator(1) returning -1, not 0. Ties round toward floor regardless of sign, which for negative numbers means away from zero, matching math.floor(-0.5) == -1.

What rounding mode does Fraction.limit_denominator() use?

Based on what we measured (every exact tie landing on the floor value), it behaves like round-half-down-toward-floor, not round-half-up, round-half-to-even, or round-half-away-from-zero. If a specific rounding rule matters, round explicitly with decimal.Decimal.quantize() and a named rounding mode instead.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in fractions module: called limit_denominator(1) (rounding to the nearest whole number) on Fractions that land on an exact .5 halfway point, for both positive and negative values, and compared the results to Python's built-in round() on the equivalent floats. PlainHeaders is our extension; Python's fractions module is built in.