diff --git a/src/humanize/number.py b/src/humanize/number.py index 52a5356a..09a6b3fe 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -130,10 +130,14 @@ def ordinal(value: NumberOrString, gender: str = "male") -> str: import math try: - if not math.isfinite(float(value)): - return _format_not_finite(float(value)) value = int(value) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): + try: + float_value = float(value) + except (TypeError, ValueError): + return str(value) + if not math.isfinite(float_value): + return _format_not_finite(float_value) return str(value) gender = "male" if gender == "male" else "female" digit = 0 if value % 100 in (11, 12, 13) else value % 10 diff --git a/tests/test_number.py b/tests/test_number.py index 5fb12fa6..89e652ae 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -4,6 +4,7 @@ import math import typing +from decimal import Decimal import pytest @@ -25,6 +26,12 @@ ("102", "102nd"), ("103", "103rd"), ("111", "111th"), + (21.9, "21st"), + (Decimal("21.9"), "21st"), + (True, "1st"), + (False, "0th"), + ("21.9", "21.9"), + ("1e3", "1e3"), ("something else", "something else"), (None, "None"), (math.nan, "NaN"), @@ -38,6 +45,26 @@ def test_ordinal(test_input: str, expected: str) -> None: assert humanize.ordinal(test_input) == expected +@pytest.mark.parametrize("convert", [int, str, Decimal]) +@pytest.mark.parametrize( + "last_digits, suffix", + [(1, "st"), (2, "nd"), (3, "rd"), (11, "th"), (12, "th"), (13, "th")], +) +def test_ordinal_large_integers( + convert: typing.Callable[[int], typing.Any], last_digits: int, suffix: str +) -> None: + value = 10**400 + last_digits + assert humanize.ordinal(convert(value)) == f"{value}{suffix}" + + +def test_ordinal_int_only_object() -> None: + class IntOnly: + def __int__(self) -> int: + return 21 + + assert humanize.ordinal(typing.cast("typing.Any", IntOnly())) == "21st" + + @pytest.mark.parametrize( "test_args, expected", [