From c0d6e18ff15887e871fe5aeebd2ccbb1b26ec91e Mon Sep 17 00:00:00 2001 From: mmaxjr Date: Thu, 30 Jul 2026 09:55:54 -0300 Subject: [PATCH] fix(intword): correct decillion-to-googol gap and carry-over rounding `powers` jumps from decillion (10**33) directly to googol (10**100), so any value in [10**36, 10**100) was chopped into a decillion count that grew arbitrarily large (e.g. 10**50 -> "100000000000000000.0 decillion"), and values that should round up into googol (e.g. 10**100 - 10**93) never carried over. Only advance to the next named unit when the value actually rounds up to it; otherwise there is no unit for that magnitude, so fall back to the plain integer, mirroring how intword() already falls back to a plain value below the smallest unit. Fixes #356 Co-Authored-By: Claude Sonnet 4.6 --- src/humanize/number.py | 22 ++++++++++++++++++---- tests/test_number.py | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 2fb22c6..bc24a4b 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -255,10 +255,24 @@ def intword(value: NumberOrString, format: str = "%.1f") -> str: chopped = value / power rounded_value = float(format % chopped) - if not largest_ordinal and rounded_value * power == powers[ordinal + 1]: - # After rounding, we end up just at the next power - ordinal += 1 - rounded_value = 1.0 + if not largest_ordinal and rounded_value >= 1000: + next_power = powers[ordinal + 1] + if next_power == power * 1000: + # After rounding, we end up just at the next power + ordinal += 1 + rounded_value = 1.0 + else: + # `powers` has a gap between this unit and the next one (e.g. + # decillion to googol), so there is no unit for "1000+ of the + # current one". Only bump to the next unit if the value is + # close enough to actually round up to it; otherwise there is + # no name for this magnitude, so fall back to the plain number. + next_rounded_value = float(format % (value / next_power)) + if next_rounded_value >= 1.0: + ordinal += 1 + rounded_value = next_rounded_value + else: + return f"{negative_prefix}{value}" singular, plural = human_powers[ordinal] unit = _ngettext(singular, plural, math.ceil(rounded_value)) diff --git a/tests/test_number.py b/tests/test_number.py index 78639c3..a731a83 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -116,10 +116,21 @@ def test_intword_powers() -> None: (["3500000000000000000000"], "3.5 sextillion"), (["8100000000000000000000000000000000"], "8.1 decillion"), (["-8100000000000000000000000000000000"], "-8.1 decillion"), - ([1_000_000_000_000_000_000_000_000_000_000_000_000], "1000.0 decillion"), - ([1_100_000_000_000_000_000_000_000_000_000_000_000], "1100.0 decillion"), - ([2_100_000_000_000_000_000_000_000_000_000_000_000], "2100.0 decillion"), + ( + [1_000_000_000_000_000_000_000_000_000_000_000_000], + "1000000000000000000000000000000000000", + ), + ( + [1_100_000_000_000_000_000_000_000_000_000_000_000], + "1100000000000000000000000000000000000", + ), + ( + [2_100_000_000_000_000_000_000_000_000_000_000_000], + "2100000000000000000000000000000000000", + ), ([2e100], "2.0 googol"), + ([10**50], str(10**50)), + ([10**100 - 10**93], "1.0 googol"), ([None], "None"), (["1230000", "%0.2f"], "1.23 million"), ([10**100], "1.0 googol"),