https://github.com/python/cpython/commit/90a5b4402bc6b8f44a9fe9a5299721b1887b50f8
commit: 90a5b4402bc6b8f44a9fe9a5299721b1887b50f8
branch: main
author: Sergey B Kirpichev <skirpic...@gmail.com>
committer: hugovk <1324225+hug...@users.noreply.github.com>
date: 2025-07-07T11:16:27+03:00
summary:

gh-87790: support thousands separators for formatting fractional part of 
Decimal (#132202)

Co-authored-by: Serhiy Storchaka <storch...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-04-07-09-53-54.gh-issue-87790.6nj3zQ.rst
M Lib/_pydecimal.py
M Lib/test/test_decimal.py

diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index 781b38ec26ba33..9b8e42a2342536 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -6122,7 +6122,11 @@ def _convert_for_comparison(self, other, 
equality_op=False):
 (?P<zeropad>0)?
 (?P<minimumwidth>\d+)?
 (?P<thousands_sep>[,_])?
-(?:\.(?P<precision>\d+))?
+(?:\.
+    (?=[\d,_])  # lookahead for digit or separator
+    (?P<precision>\d+)?
+    (?P<frac_separators>[,_])?
+)?
 (?P<type>[eEfFgGn%])?
 \z
 """, re.VERBOSE|re.DOTALL)
@@ -6215,6 +6219,9 @@ def _parse_format_specifier(format_spec, 
_localeconv=None):
         format_dict['grouping'] = [3, 0]
         format_dict['decimal_point'] = '.'
 
+    if format_dict['frac_separators'] is None:
+        format_dict['frac_separators'] = ''
+
     return format_dict
 
 def _format_align(sign, body, spec):
@@ -6334,6 +6341,11 @@ def _format_number(is_negative, intpart, fracpart, exp, 
spec):
 
     sign = _format_sign(is_negative, spec)
 
+    frac_sep = spec['frac_separators']
+    if fracpart and frac_sep:
+        fracpart = frac_sep.join(fracpart[pos:pos + 3]
+                                 for pos in range(0, len(fracpart), 3))
+
     if fracpart or spec['alt']:
         fracpart = spec['decimal_point'] + fracpart
 
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index ef64b878805d77..08a8f4c3b36bd6 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -1089,6 +1089,15 @@ def test_formatting(self):
             ('07_', '1234.56', '1_234.56'),
             ('_', '1.23456789', '1.23456789'),
             ('_%', '123.456789', '12_345.6789%'),
+            # and now for something completely different...
+            ('.,', '1.23456789', '1.234,567,89'),
+            ('._', '1.23456789', '1.234_567_89'),
+            ('.6_f', '12345.23456789', '12345.234_568'),
+            (',._%', '123.456789', '12,345.678_9%'),
+            (',._e', '123456', '1.234_56e+5'),
+            (',.4_e', '123456', '1.234_6e+5'),
+            (',.3_e', '123456', '1.235e+5'),
+            (',._E', '123456', '1.234_56E+5'),
 
             # negative zero: default behavior
             ('.1f', '-0', '-0.0'),
@@ -1162,6 +1171,10 @@ def test_formatting(self):
         # bytes format argument
         self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
 
+        # precision or fractional part separator should follow after dot
+        self.assertRaises(ValueError, format, Decimal(1), '.f')
+        self.assertRaises(ValueError, format, Decimal(1), '._6f')
+
     def test_negative_zero_format_directed_rounding(self):
         with self.decimal.localcontext() as ctx:
             ctx.rounding = ROUND_CEILING
diff --git 
a/Misc/NEWS.d/next/Library/2025-04-07-09-53-54.gh-issue-87790.6nj3zQ.rst 
b/Misc/NEWS.d/next/Library/2025-04-07-09-53-54.gh-issue-87790.6nj3zQ.rst
new file mode 100644
index 00000000000000..cf80c71271bbd1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-07-09-53-54.gh-issue-87790.6nj3zQ.rst
@@ -0,0 +1,2 @@
+Support underscore and comma as thousands separators in the fractional part
+for :class:`~decimal.Decimal`'s formatting.  Patch by Sergey B Kirpichev.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to