From 8cc8c80bb198f2b482b89d90644b036e02508b05 Mon Sep 17 00:00:00 2001 From: beduldul Date: Sun, 25 Oct 2026 18:12:53 +0700 Subject: [PATCH] Don't raise OverflowError for ints larger than a float Six functions in humanize.number call float(value) just to test for NaN and infinity before converting to int. For an int outside the float range that conversion raises OverflowError, which isn't in the except clause, so it escapes to the caller: >>> humanize.apnumber(10**400) OverflowError: int too large to convert to float Same for intword, ordinal, metric, scientific and fractional. intcomma is unaffected because it skips the float conversion when the value is an int. Catch OverflowError alongside TypeError and ValueError and fall back to str(value), which is what the docstrings already promise for values that can't be converted. metric had no try block at all, so it gets one. --- src/humanize/number.py | 18 +++++++++++------- tests/test_number.py | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 52a5356a..cd32ebb6 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -133,7 +133,7 @@ def ordinal(value: NumberOrString, gender: str = "male") -> str: if not math.isfinite(float(value)): return _format_not_finite(float(value)) value = int(value) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): return str(value) gender = "male" if gender == "male" else "female" digit = 0 if value % 100 in (11, 12, 13) else value % 10 @@ -261,7 +261,7 @@ def intword(value: NumberOrString, format: str = "%.1f") -> str: if not math.isfinite(float(value)): return _format_not_finite(float(value)) value = int(value) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): return str(value) if value < 0: @@ -332,7 +332,7 @@ def apnumber(value: NumberOrString) -> str: if not math.isfinite(float(value)): return _format_not_finite(float(value)) value = int(value) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): return str(value) if not 0 <= value < 10: return str(value) @@ -384,7 +384,7 @@ def fractional(value: NumberOrString) -> str: number = float(value) if not math.isfinite(number): return _format_not_finite(number) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): return str(value) from fractions import Fraction @@ -447,7 +447,7 @@ def scientific(value: NumberOrString, precision: int = 2) -> str: value = float(value) if not math.isfinite(value): return _format_not_finite(value) - except (ValueError, TypeError): + except (ValueError, TypeError, OverflowError): return str(value) fmt = f"{{:.{int(precision)}e}}" n = fmt.format(value) @@ -571,8 +571,12 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str: """ import math - if not math.isfinite(value): - return _format_not_finite(value) + try: + if not math.isfinite(value): + return _format_not_finite(value) + except OverflowError: + # An int too large for float() is still a valid number. + return str(value) exponent = math.floor(math.log10(abs(value))) if value != 0 else 0 if exponent >= 33 or exponent < -30: diff --git a/tests/test_number.py b/tests/test_number.py index 5fb12fa6..18fe9c39 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -377,3 +377,19 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: ) def test_metric(test_args: list[typing.Any], expected: str) -> None: assert humanize.metric(*test_args) == expected + + +@pytest.mark.parametrize( + "function", + [ + humanize.apnumber, + humanize.intword, + humanize.ordinal, + humanize.metric, + humanize.scientific, + humanize.fractional, + ], +) +def test_large_int_does_not_overflow(function: typing.Callable[[int], str]) -> None: + value = 10**400 + assert function(value) == str(value)