Summary
Most functions in humanize.number convert the input with float() before trying int(), purely to test for NaN/infinity. For an integer larger than the float range that conversion raises OverflowError, which escapes the except (TypeError, ValueError) clause and propagates to the caller — even though the value is a perfectly ordinary Python int.
Reproduction
>>> import humanize
>>> humanize.apnumber(10**400)
OverflowError: int too large to convert to float
>>> humanize.fractional(10**400)
OverflowError: int too large to convert to float
Scope
Passing 10**400 + 123 on main:
| function |
as int |
as str |
ordinal |
OverflowError |
+Inf |
intcomma |
correct |
+Inf |
intword |
OverflowError |
+Inf |
apnumber |
OverflowError |
+Inf |
fractional |
OverflowError |
+Inf |
scientific |
OverflowError |
+Inf |
clamp |
OverflowError |
TypeError |
metric |
OverflowError |
TypeError |
The +Inf results are the same root cause seen from the string side: float("1" + "0"*400) returns inf rather than raising, so a finite integer is reported as infinite.
Why these are bugs rather than out-of-contract input
apnumber documents the fallback explicitly:
This always returns a string unless the value was not int-able, then str(value) is returned.
10**400 is int-able — it is already an int — so by the documented contract it must return a string. ordinal carries the same wording. The others follow the same except ...: return str(value) shape, so graceful degradation is clearly the intent throughout; OverflowError simply isn't in the tuple.
clamp and metric annotate value: float, so arguably a large int is outside their contract — I've listed them for completeness rather than as a claim.
Related work already in flight
So apnumber, fractional and scientific are the ones not currently covered by an open PR. I'll put up a PR for apnumber and fractional, where the correct result is unambiguous.
scientific is worth separating: falling back to str(value) there would return 401 digits from a function whose purpose is compact notation, so it probably wants Decimal-based formatting (1.00 x 10⁴⁰⁰) rather than a fallback. Happy to follow up with that if the approach sounds right.
Verified on main at 392aef7, Python 3.12.
Summary
Most functions in
humanize.numberconvert the input withfloat()before tryingint(), purely to test for NaN/infinity. For an integer larger than the float range that conversion raisesOverflowError, which escapes theexcept (TypeError, ValueError)clause and propagates to the caller — even though the value is a perfectly ordinary Pythonint.Reproduction
Scope
Passing
10**400 + 123onmain:intstrordinalOverflowError+Infintcomma+InfintwordOverflowError+InfapnumberOverflowError+InffractionalOverflowError+InfscientificOverflowError+InfclampOverflowErrorTypeErrormetricOverflowErrorTypeErrorThe
+Infresults are the same root cause seen from the string side:float("1" + "0"*400)returnsinfrather than raising, so a finite integer is reported as infinite.Why these are bugs rather than out-of-contract input
apnumberdocuments the fallback explicitly:10**400is int-able — it is already anint— so by the documented contract it must return a string.ordinalcarries the same wording. The others follow the sameexcept ...: return str(value)shape, so graceful degradation is clearly the intent throughout;OverflowErrorsimply isn't in the tuple.clampandmetricannotatevalue: float, so arguably a largeintis outside their contract — I've listed them for completeness rather than as a claim.Related work already in flight
ordinalby tryingint()beforefloat()intcommastring caseintwordSo
apnumber,fractionalandscientificare the ones not currently covered by an open PR. I'll put up a PR forapnumberandfractional, where the correct result is unambiguous.scientificis worth separating: falling back tostr(value)there would return 401 digits from a function whose purpose is compact notation, so it probably wantsDecimal-based formatting (1.00 x 10⁴⁰⁰) rather than a fallback. Happy to follow up with that if the approach sounds right.Verified on
mainat 392aef7, Python 3.12.