diff --git a/src/humanize/number.py b/src/humanize/number.py index 2dfeffe4..b169fc26 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -550,8 +550,9 @@ 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 + # Absorb a rounding carry (9.999 -> 10.0) by bumping the exponent. + 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 6f0a664a..755e29ea 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -270,6 +270,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"),