]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-87790: support thousands separators for formatting fractional part of Decimal...
authorSergey B Kirpichev <skirpichev@gmail.com>
Mon, 7 Jul 2025 08:16:27 +0000 (11:16 +0300)
committerGitHub <noreply@github.com>
Mon, 7 Jul 2025 08:16:27 +0000 (11:16 +0300)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/_pydecimal.py
Lib/test/test_decimal.py
Misc/NEWS.d/next/Library/2025-04-07-09-53-54.gh-issue-87790.6nj3zQ.rst [new file with mode: 0644]

index 781b38ec26ba33bba3fccf5aad2bce2b8b3fb001..9b8e42a2342536b48ae2b0afcaf2faeaf93160d8 100644 (file)
@@ -6122,7 +6122,11 @@ _parse_format_specifier_regex = re.compile(r"""\A
 (?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
 
index ef64b878805d77a1260ca8f1e3be0471e0ef1f67..08a8f4c3b36bd677656f54688edee651ff348435 100644 (file)
@@ -1089,6 +1089,15 @@ class FormatTest:
             ('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 @@ class FormatTest:
         # 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 (file)
index 0000000..cf80c71
--- /dev/null
@@ -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.