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"),