diff --git a/src/humanize/number.py b/src/humanize/number.py index 52a5356a..b1e62f18 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -578,12 +578,16 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str: if exponent >= 33 or exponent < -30: return scientific(value, precision - 1) + unit + original = value old_bucket = exponent // 3 * 3 value /= 10**old_bucket digits = int(max(0, precision - exponent % 3 - 1)) # Absorb a rounding carry (9.999 -> 10.0) by bumping the exponent. - if exponent < 30 and round(abs(value), digits) >= 10 ** (exponent % 3 + 1): + if round(abs(value), digits) >= 10 ** (exponent % 3 + 1): exponent += 1 + if exponent >= 33: + # Rounded past quetta: no larger prefix exists. + return scientific(original, precision - 1) + unit new_bucket = exponent // 3 * 3 value /= 10 ** (new_bucket - old_bucket) digits = int(max(0, precision - exponent % 3 - 1)) diff --git a/tests/test_number.py b/tests/test_number.py index 5fb12fa6..d940f8f1 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -332,6 +332,12 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: ([1.235e29, "A"], "124 RA"), ([2.56e-30, "V"], "2.56 qV"), ([2.596e32, "F"], "260 QF"), + # Rounding carry at the quetta boundary (issue #423) + ([9.996e30], "10.0 Q"), + ([9.996e31], "100 Q"), + ([9.996e32], "1.00 x 10\u00b3\u00b3"), + ([9.999e-30], "10.0 q"), + ([-9.996e31], "-100 Q"), ([1e50], "1.00 x 10⁵⁰"), ([1e-50], "1.00 x 10⁻⁵⁰"), ([-1500, "V"], "-1.50 kV"),