From 56f4f71b98dfb499c974b0d12b8f68719c2eb102 Mon Sep 17 00:00:00 2001 From: Vidit Patankar Date: Sat, 25 Jul 2026 14:03:42 +0530 Subject: [PATCH] Fix metric() showing an extra significant digit on rounding carry metric() derives the number of decimal places from the mantissa's exponent, assuming the mantissa keeps its digit count. When rounding carries it up a power of ten (9.999 -> 10.0, 99.99 -> 100) it gains an integer digit and shows one significant figure too many, e.g. metric(9999) returned '10.00 k' instead of '10.0 k'. The existing guard only handled the mantissa reaching 1000 (a full SI-bucket crossing). Detect the carry against the next power of ten and bump the exponent by one, which recomputes the decimal places and, when the mantissa reaches 1000, still crosses into the next bucket exactly as before. --- src/humanize/number.py | 9 +++++++-- tests/test_number.py | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 2fb22c6..80f59b9 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -548,8 +548,13 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str: old_bucket = exponent // 3 * 3 value /= 10**old_bucket digits = int(max(0, precision - exponent % 3 - 1)) - if exponent < 30 and round(abs(value), digits) >= 1000: - exponent += 3 - exponent % 3 + # Rounding to ``digits`` decimal places can carry the mantissa up to the + # next power of ten (e.g. 9.999 -> 10.0, 99.99 -> 100, 999.9 -> 1000), + # which adds an integer digit and would otherwise show one significant + # figure too many. Bump the exponent to absorb the carry -- crossing into + # the next SI bucket when the mantissa reaches 1000 -- and recompute. + if exponent < 30 and round(abs(value), digits) >= 10 ** (exponent % 3 + 1): + exponent += 1 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 78639c3..223f2c8 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -258,6 +258,15 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: ([1234.56], "1.23 k"), ([12345, "", 6], "12.3450 k"), ([200_000], "200 k"), + # Rounding that carries the mantissa up a power of ten within a + # bucket must not add an extra significant figure (issue: metric + # showed one digit too many for e.g. 9999 -> "10.00 k"). + ([9999], "10.0 k"), + ([99999], "100 k"), + ([9.99999], "10.0"), + ([-9999], "-10.0 k"), + ([9.999, "", 2], "10"), + ([999.4], "999"), ([999.9, "V"], "1.00 kV"), ([999.99, "V"], "1.00 kV"), ([999_999, "V"], "1.00 MV"),