Skip to content

intword() drops the rounding carry for values >= 10^24 (renders '1000.0 sextillion' instead of '1.0 septillion') #400

Description

@Sreekant13

intword() has a rounding-carry step so that a value just under a power boundary reads as the next unit: 999_999 becomes "1.0 million", not "1000.0 thousand". That carry silently stops working from septillion (10^24) upward, so large numbers just under a boundary render as "1000.0 <smaller unit>".

Version: humanize main (4.x). Python 3.10+ (independent of version; it is a float-representation issue).

Repro

from humanize import intword

print(intword(10**24 - 1))   # '1000.0 sextillion'   <- want '1.0 septillion'
print(intword(10**27 - 1))   # '1000.0 septillion'   <- want '1.0 octillion'
print(intword(10**30 - 1))   # '1000.0 octillion'    <- want '1.0 nonillion'
print(intword(10**33 - 1))   # '1000.0 nonillion'    <- want '1.0 decillion'

For comparison, the same shape one bucket lower is fine, because the carry still fires there:

print(intword(10**9 - 1))    # '1.0 billion'   (correct)
print(intword(10**21 - 1))   # '1.0 sextillion' (correct)

So the boundary is exactly 10**24: 10**6 .. 10**21 carry, 10**24 .. 10**33 do not.

Root cause

The carry check multiplies the rounded mantissa (a float) by the power and compares it to the next power as an exact int:

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

powers holds Python ints (10**24, 10**27, ...), but float(10**k) != 10**k once k >= 24 (a float has 52 bits of mantissa, and 10**24 needs more). So for 10**24 - 1:

rounded_value = 1000.0            # float("%.1f" % 999.999...)
power         = 10**21
rounded_value * power == 10**24   # -> False: LHS is an inexact float, RHS an exact int

The equality is False, the carry is skipped, and the output keeps the smaller unit with a 1000.0 mantissa. Below 10**24 the intermediate float(10**k) is exact, so the same expression is True and the carry works, which is why this went unnoticed.

>>> 1000.0 * 10**21 == 10**24
False
>>> 1000.0 * 10**18 == 10**21
True

Not the same as #356

#356 is about the gap between decillion (10**33) and googol (10**100), where there is no unit to use and values render as large decillion counts (e.g. 10**36 -> "1000.0 decillion"). That is a missing-unit question. This issue is a plain carry that should fire and does not, purely because of the float-vs-int comparison; 10**24 through 10**33 all have a real next unit to carry into.

Fix

Compare the rounded mantissa against the integer ratio between adjacent powers instead of multiplying a float by a large int:

if not largest_ordinal and rounded_value >= powers[ordinal + 1] // power:
    ordinal += 1
    rounded_value = 1.0

Every entry in powers is a power of ten, so powers[ordinal + 1] // power is exact, and it stays large (10**67) across the decillion-to-googol gap, so 10**36 is still left as "1000.0 decillion" (the #356 behaviour is unchanged). PR with tests to follow.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions