From 02dc4d2322572cef787c938c1cb3208f7f9c8ae9 Mon Sep 17 00:00:00 2001 From: gaoflow Date: Tue, 16 Jun 2026 16:04:02 +0200 Subject: [PATCH 1/2] Carry metric() to the next SI prefix when rounding reaches 1000 metric() chose the SI prefix from floor(log10(value)) on the raw value and then rounded the mantissa to the requested precision. For a value just below a power-of-1000 boundary the rounded mantissa became 1000 while the prefix stayed put, so metric(999.9, "V") returned "1000 V" and metric(999999, "V") returned "1000 kV". That is exactly the "1230K"-style output the function's docstring promises to avoid. After rounding, if the mantissa reaches 1000 and a higher prefix is available (exponent < 30), advance to the next prefix bucket and re-divide. The top of the SI range (quetta) has no higher prefix, so it is left unchanged. Output for values not on a rounding boundary is unaffected. --- src/humanize/number.py | 11 +++++++++-- tests/test_number.py | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index f4dcb05d..0f907cea 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -538,14 +538,21 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str: if exponent >= 33 or exponent < -30: return scientific(value, precision - 1) + unit - value /= 10 ** (exponent // 3 * 3) + 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 + new_bucket = exponent // 3 * 3 + value /= 10 ** (new_bucket - old_bucket) + digits = int(max(0, precision - exponent % 3 - 1)) if exponent >= 3: ordinal_ = "kMGTPEZYRQ"[exponent // 3 - 1] elif exponent < 0: ordinal_ = "mμnpfazyrq"[(-exponent - 1) // 3] else: ordinal_ = "" - value_ = format(value, f".{int(max(0, precision - exponent % 3 - 1))}f") + value_ = format(value, f".{digits}f") if not (unit or ordinal_) or unit in ("°", "′", "″"): space = "" else: diff --git a/tests/test_number.py b/tests/test_number.py index cb74fcf0..cd7c73c1 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -254,6 +254,10 @@ 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"), + ([999.9, "V"], "1.00 kV"), + ([999.99, "V"], "1.00 kV"), + ([999_999, "V"], "1.00 MV"), + ([0.0009999, "V"], "1.00 mV"), ([1e25, "m"], "10.0 Ym"), ([1e26, "m"], "100 Ym"), ([1e27, "A"], "1.00 RA"), From 4b69e613e6d9ce02267c45b5f69f8d9cc780cef7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:31:08 +0300 Subject: [PATCH 2/2] Add newline after if block --- src/humanize/number.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/humanize/number.py b/src/humanize/number.py index 0f907cea..66cf5882 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -546,6 +546,7 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str: new_bucket = exponent // 3 * 3 value /= 10 ** (new_bucket - old_bucket) digits = int(max(0, precision - exponent % 3 - 1)) + if exponent >= 3: ordinal_ = "kMGTPEZYRQ"[exponent // 3 - 1] elif exponent < 0: