From 25087f91da8e12f53a2d6eae8f19fb077fc23c1c Mon Sep 17 00:00:00 2001 From: ANSHUL SINGH <72524975+ekanshul@users.noreply.github.com> Date: Thu, 5 Nov 2026 22:26:30 +0530 Subject: [PATCH] Fix intcomma() digit loss on large decimal strings Fixes https://github.com/python-humanize/humanize/issues/496 intcomma() converted decimal strings with float(value), losing precision on large values (e.g. '1234567890123456.78' -> '...456.8'). Keep the exact string for the ndigits=None path and use Decimal when ndigits is given, preserving all input digits. Co-authored-by: CommandCodeBot --- src/humanize/number.py | 20 ++++++++++++++++++-- tests/test_number.py | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 52a5356a..36f06669 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -175,16 +175,20 @@ def intcomma(value: NumberOrString, ndigits: int | None = None) -> str: str: String containing commas every three digits. """ import math + from decimal import Decimal thousands_sep = thousands_separator() decimal_sep = decimal_separator() + is_str_decimal = False try: if isinstance(value, str): value = value.replace(thousands_sep, "").replace(decimal_sep, ".") if not math.isfinite(float(value)): return _format_not_finite(float(value)) if "." in value: - value = float(value) + # Keep the exact decimal string to avoid float() rounding + # large values, e.g. '1234567890123456.78'. + is_str_decimal = True else: value = int(value) elif not isinstance(value, int): @@ -195,9 +199,21 @@ def intcomma(value: NumberOrString, ndigits: int | None = None) -> str: return str(value) if ndigits is not None: + if is_str_decimal: + value = Decimal(value) result = f"{value:,.{ndigits}f}" else: - result = f"{value:,}" + if is_str_decimal: + # Format the original digit string directly so no precision is lost. + int_part, _, frac_part = value.partition(".") + sign = "" + if int_part.startswith(("-", "+")): + sign, int_part = int_part[0], int_part[1:] + int_part = int_part.lstrip("0") or "0" + grouped = f"{int(int_part):,}" if int_part else "0" + result = f"{sign}{grouped}.{frac_part}" if frac_part else f"{sign}{grouped}" + else: + result = f"{value:,}" if thousands_sep != "," or decimal_sep != ".": result = result.translate(str.maketrans(",.", thousands_sep + decimal_sep)) return result diff --git a/tests/test_number.py b/tests/test_number.py index 5fb12fa6..10888e8a 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -53,6 +53,8 @@ def test_ordinal(test_input: str, expected: str) -> None: (["10311"], "10,311"), (["1000000"], "1,000,000"), (["1234567.1234567"], "1,234,567.1234567"), + (["1234567890123456.78"], "1,234,567,890,123,456.78"), + (["1234567890123456.78", 2], "1,234,567,890,123,456.78"), (["1234567.1234567", 0], "1,234,567"), (["1234567.1234567", 1], "1,234,567.1"), (["1234567.1234567", 10], "1,234,567.1234567000"),