From 0fe540de320a1f2d5ed928de3fc58d127cf46571 Mon Sep 17 00:00:00 2001 From: Sergey Sannikov Date: Mon, 20 Jul 2026 16:07:44 +0400 Subject: [PATCH] Fix fractional() emitting degenerate output when the fraction rounds to a whole When limit_denominator(1000) reduces the fractional part to a whole number (denominator == 1), fold it into the integer part instead of printing a degenerate "N/1". For example fractional(2.9999999) returned "2 1/1" instead of "3", fractional(0.9999999) returned "1/1" instead of "1", and fractional(0) returned "0/1" instead of "0". --- src/humanize/number.py | 10 ++++++---- tests/test_number.py | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 2fb22c6..7771a15 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -361,10 +361,12 @@ def fractional(value: NumberOrString) -> str: frac = Fraction(number - whole_number).limit_denominator(1000) numerator = frac.numerator denominator = frac.denominator - if whole_number and not numerator and denominator == 1: - # this means that an integer was passed in - # (or variants of that integer like 1.0000) - return f"{whole_number:.0f}" + if denominator == 1: + # The fractional part reduced to a whole number: either an integer + # was passed in (e.g. 1 or 1.0000, giving 0/1), or the fractional part + # rounded up to 1/1 (e.g. 2.9999999). Fold it into the integer part + # instead of emitting a degenerate "0/1" or "2 1/1". + return f"{whole_number + numerator:.0f}" if not whole_number: return f"{numerator:.0f}/{denominator:.0f}" diff --git a/tests/test_number.py b/tests/test_number.py index 78639c3..cf55ee1 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -187,6 +187,11 @@ def test_apnumber(test_input: int | str, expected: str) -> None: (-1.3, "-1 3/10"), (-2.5, "-2 1/2"), (-0.5, "-1/2"), + (0, "0"), + (0.0, "0"), + (2.9999999, "3"), + (0.9999999, "1"), + (-2.9999999, "-3"), ], ) def test_fractional(test_input: float | str, expected: str) -> None: