From b9c19662891687c27cdee15debcf1ac4a7d099fc Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:57:19 -0700 Subject: [PATCH 1/2] Clamp negative precision in scientific() scientific() built its format spec as f"{{:.{int(precision)}e}}", so a negative precision produced an invalid spec ("{:.-1e}") and raised ValueError. This is reachable from the public API through metric(value, precision=0): for magnitudes that fall back to scientific notation, metric() calls scientific(value, precision - 1), i.e. scientific(value, -1). PR #159 added the same max(0, ...) clamp to metric()'s main path but not to scientific(). Clamp the precision to zero. --- src/humanize/number.py | 3 ++- tests/test_number.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 2fb22c6..d6f7e9e 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -416,7 +416,8 @@ def scientific(value: NumberOrString, precision: int = 2) -> str: return _format_not_finite(value) except (ValueError, TypeError): return str(value) - fmt = f"{{:.{int(precision)}e}}" + # max(0): a negative precision builds an invalid format spec ("{:.-1e}"). + fmt = f"{{:.{max(0, int(precision))}e}}" n = fmt.format(value) part1, part2 = n.split("e") # Normalise exponent: int() strips the "+" sign and leading zeros, diff --git a/tests/test_number.py b/tests/test_number.py index 78639c3..a9ac79d 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -218,6 +218,10 @@ def test_fractional(test_input: float | str, expected: str) -> None: ([-math.inf], "-Inf"), (["nan"], "NaN"), (["-inf"], "-Inf"), + # Negative precision clamps to zero instead of building an invalid spec. + ([1e300, -1], "1 x 10³⁰⁰"), + ([1e300, -5], "1 x 10³⁰⁰"), + ([-1000, -1], "-1 x 10³"), ], ) def test_scientific(test_args: list[typing.Any], expected: str) -> None: @@ -310,6 +314,11 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: ([math.nan, "m"], "NaN"), ([math.inf], "+Inf"), ([-math.inf], "-Inf"), + # precision=0 on a scientific-fallback magnitude passed -1 to scientific(); see #159. + ([1e300, "m", 0], "1 x 10³⁰⁰m"), + ([-1e300, "m", 0], "-1 x 10³⁰⁰m"), + ([1e-300, "m", 0], "1 x 10⁻³⁰⁰m"), + ([1e40, "", 0], "1 x 10⁴⁰"), ], ids=str, ) From c6782b5dffccba33e623c7cfbfa05f14cea1a905 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:28:24 -0700 Subject: [PATCH 2/2] Fix E501 on scientific precision=0 test comment. --- tests/test_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_number.py b/tests/test_number.py index a9ac79d..0d1483f 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -314,7 +314,7 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: ([math.nan, "m"], "NaN"), ([math.inf], "+Inf"), ([-math.inf], "-Inf"), - # precision=0 on a scientific-fallback magnitude passed -1 to scientific(); see #159. + # precision=0 scientific fallback used to pass -1 into scientific(); see #159. ([1e300, "m", 0], "1 x 10³⁰⁰m"), ([-1e300, "m", 0], "-1 x 10³⁰⁰m"), ([1e-300, "m", 0], "1 x 10⁻³⁰⁰m"),