Skip to content

Commit 90a5b44

Browse files
gh-87790: support thousands separators for formatting fractional part of Decimal (#132202)
Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 0c3e3da commit 90a5b44

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

Lib/_pydecimal.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6122,7 +6122,11 @@ def _convert_for_comparison(self, other, equality_op=False):
61226122
(?P<zeropad>0)?
61236123
(?P<minimumwidth>\d+)?
61246124
(?P<thousands_sep>[,_])?
6125-
(?:\.(?P<precision>\d+))?
6125+
(?:\.
6126+
(?=[\d,_]) # lookahead for digit or separator
6127+
(?P<precision>\d+)?
6128+
(?P<frac_separators>[,_])?
6129+
)?
61266130
(?P<type>[eEfFgGn%])?
61276131
\z
61286132
""", re.VERBOSE|re.DOTALL)
@@ -6215,6 +6219,9 @@ def _parse_format_specifier(format_spec, _localeconv=None):
62156219
format_dict['grouping'] = [3, 0]
62166220
format_dict['decimal_point'] = '.'
62176221

6222+
if format_dict['frac_separators'] is None:
6223+
format_dict['frac_separators'] = ''
6224+
62186225
return format_dict
62196226

62206227
def _format_align(sign, body, spec):
@@ -6334,6 +6341,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
63346341

63356342
sign = _format_sign(is_negative, spec)
63366343

6344+
frac_sep = spec['frac_separators']
6345+
if fracpart and frac_sep:
6346+
fracpart = frac_sep.join(fracpart[pos:pos + 3]
6347+
for pos in range(0, len(fracpart), 3))
6348+
63376349
if fracpart or spec['alt']:
63386350
fracpart = spec['decimal_point'] + fracpart
63396351

Lib/test/test_decimal.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,15 @@ def test_formatting(self):
10891089
('07_', '1234.56', '1_234.56'),
10901090
('_', '1.23456789', '1.23456789'),
10911091
('_%', '123.456789', '12_345.6789%'),
1092+
# and now for something completely different...
1093+
('.,', '1.23456789', '1.234,567,89'),
1094+
('._', '1.23456789', '1.234_567_89'),
1095+
('.6_f', '12345.23456789', '12345.234_568'),
1096+
(',._%', '123.456789', '12,345.678_9%'),
1097+
(',._e', '123456', '1.234_56e+5'),
1098+
(',.4_e', '123456', '1.234_6e+5'),
1099+
(',.3_e', '123456', '1.235e+5'),
1100+
(',._E', '123456', '1.234_56E+5'),
10921101

10931102
# negative zero: default behavior
10941103
('.1f', '-0', '-0.0'),
@@ -1162,6 +1171,10 @@ def test_formatting(self):
11621171
# bytes format argument
11631172
self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
11641173

1174+
# precision or fractional part separator should follow after dot
1175+
self.assertRaises(ValueError, format, Decimal(1), '.f')
1176+
self.assertRaises(ValueError, format, Decimal(1), '._6f')
1177+
11651178
def test_negative_zero_format_directed_rounding(self):
11661179
with self.decimal.localcontext() as ctx:
11671180
ctx.rounding = ROUND_CEILING
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Support underscore and comma as thousands separators in the fractional part
2+
for :class:`~decimal.Decimal`'s formatting. Patch by Sergey B Kirpichev.

0 commit comments

Comments
 (0)