Skip to content

Fix intword() rounding carry for very large numbers - #346

Merged
hugovk merged 1 commit into
python-humanize:mainfrom
binggao1230:fix-intword-carry-rollover
Sep 16, 2026
Merged

hugovk merged 1 commit into
python-humanize:mainfrom
binggao1230:fix-intword-carry-rollover

Conversation

@binggao1230

Copy link
Copy Markdown
Contributor

Problem

intword() decides whether rounding pushed a value up into the next magnitude
with:

if not largest_ordinal and rounded_value * power == powers[ordinal + 1]:

For values above ~10**22, rounded_value * power is evaluated in floating
point and no longer equals the exact powers[ordinal + 1], so the carry is
skipped and the number is rendered against the lower magnitude:

>>> import humanize
>>> humanize.intword(10**24 - 1)
'1000.0 sextillion'      # expected '1.0 septillion'
>>> humanize.intword(10**27 - 1)
'1000.0 septillion'      # expected '1.0 octillion'

The same happens at 10**30 and 10**33.

Fix

Compare rounded_value against the exact integer ratio
powers[ordinal + 1] // power, keeping the check in exact integer terms rather
than relying on a large float product. This mirrors the recently fixed carry
handling in metric() (#328) and naturalsize() (#329).

Tests

test_intword_rounding_rollover asserts the correct roll-over across every
affected magnitude (10**24/27/30/33 - 1), plus a couple of just-below cases
that must not roll over, guarding both directions.

pytest → 716 passed. ruff check / ruff format --check clean.

intword() detected the post-rounding carry into the next magnitude with
'rounded_value * power == powers[ordinal + 1]'. Above ~10**22 that product
is evaluated in floating point and no longer equals the exact next power, so
the carry was skipped and the value was rendered against the lower magnitude:
intword(10**24 - 1) returned '1000.0 sextillion' instead of '1.0 septillion'
(same for 10**27, 10**30, 10**33).

Compare rounded_value against the exact integer ratio powers[ordinal+1] // power
instead, keeping the comparison in exact integer/short-float terms. This mirrors
the recently fixed carry handling in metric() (python-humanize#328) and naturalsize() (python-humanize#329).
@codecov

codecov Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.56%. Comparing base (4c85c35) to head (eda405b).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #346   +/-   ##
=======================================
  Coverage   99.56%   99.56%           
=======================================
  Files          12       12           
  Lines         913      925   +12     
=======================================
+ Hits          909      921   +12     
  Misses          4        4           
Flag Coverage Δ
macos-latest 97.62% <100.00%> (+0.03%) ⬆️
ubuntu-latest 97.62% <100.00%> (+0.03%) ⬆️
windows-latest 95.78% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed

codspeed Bot commented Jul 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 15 untouched benchmarks


Comparing gaoflow:fix-intword-carry-rollover (eda405b) with main (4c85c35)

Open in CodSpeed

@hugovk hugovk left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import humanize

print(humanize.intword(10**24 - 1))
print(humanize.intword(10**33 - 1))
print(humanize.intword(-(10**24 - 1)))
print(humanize.intword(10**24 - 1, "%.0f"))

Before

1000.0 sextillion
1000.0 nonillion
-1000.0 sextillion
1000 sextillion

After

1.0 septillion
1.0 decillion
-1.0 septillion
1 septillion

@hugovk hugovk changed the title Fix intword() rounding carry for very large numbers Fix intword() rounding carry for very large numbers Sep 16, 2026
@hugovk
hugovk merged commit ca892b3 into python-humanize:main Sep 16, 2026
43 of 44 checks passed
Vinayak19112003 added a commit to Vinayak19112003/humanize that referenced this pull request Sep 27, 2026
Values between 1000 decillion and a googol have no named unit, so
intword() rendered them as enormous decillion counts
(e.g. intword(10**50) -> '100000000000000000.0 decillion').
A mantissa of 1000 or more now falls back to the plain number,
and values rounding up to a googol carry to '1.0 googol',
mirroring the carry rule from python-humanize#328/python-humanize#346. Closes python-humanize#356.
fitness-trener added a commit to fitness-trener/Aether that referenced this pull request Sep 30, 2026
…stream

The intword carry regression found by the 2026-07-05 port + differential
run (docs/history/REALWORLD_HUMANIZE.md) was confirmed independently:
PR python-humanize/humanize#346 by a third-party contributor, opened
2026-07-06 with the same diagnosis, merged 2026-09-16 (ca892b368a53);
#400 re-reported it and was closed as a duplicate. The fix is on main and
in no release yet (4.16.0 is current). The README gains a section that
states this with the qualifiers: a correctness bug, not a vulnerability,
and no claim that the run caused the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: Fixed For any bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants