Skip to content

Commit 9cac451

Browse files
committed
gh-87790: support thousands separators for formatting fractional part of Decimal's
1 parent ce49022 commit 9cac451

File tree

3 files changed

+27
-1
lines changed

3 files changed

+27
-1
lines changed

Lib/_pydecimal.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6099,7 +6099,11 @@ def _convert_for_comparison(self, other, equality_op=False):
60996099
(?P<zeropad>0)?
61006100
(?P<minimumwidth>(?!0)\d+)?
61016101
(?P<thousands_sep>,)?
6102-
(?:\.(?P<precision>0|(?!0)\d+))?
6102+
(?:\.
6103+
(?=\d|[,_]) # lookahead for digit or separator
6104+
(?P<precision>0|(?!0)\d+)?
6105+
(?P<frac_separators>[,_])?
6106+
)?
61036107
(?P<type>[eEfFgGn%])?
61046108
\Z
61056109
""", re.VERBOSE|re.DOTALL)
@@ -6192,6 +6196,9 @@ def _parse_format_specifier(format_spec, _localeconv=None):
61926196
format_dict['grouping'] = [3, 0]
61936197
format_dict['decimal_point'] = '.'
61946198

6199+
if format_dict['frac_separators'] is None:
6200+
format_dict['frac_separators'] = ''
6201+
61956202
return format_dict
61966203

61976204
def _format_align(sign, body, spec):
@@ -6311,6 +6318,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
63116318

63126319
sign = _format_sign(is_negative, spec)
63136320

6321+
frac_sep = spec['frac_separators']
6322+
if fracpart and frac_sep:
6323+
fracpart = frac_sep.join(fracpart[pos:pos + 3]
6324+
for pos in range(0, len(fracpart), 3))
6325+
63146326
if fracpart or spec['alt']:
63156327
fracpart = spec['decimal_point'] + fracpart
63166328

Lib/test/test_decimal.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,15 @@ def test_formatting(self):
10821082
(',%', '123.456789', '12,345.6789%'),
10831083
(',e', '123456', '1.23456e+5'),
10841084
(',E', '123456', '1.23456E+5'),
1085+
# and now for something completely different...
1086+
('.,', '1.23456789', '1.234,567,89'),
1087+
('._', '1.23456789', '1.234_567_89'),
1088+
('.6_f', '1.23456789', '1.234_568'),
1089+
(',._%', '123.456789', '12,345.678_9%'),
1090+
(',._e', '123456', '1.234_56e+5'),
1091+
(',.4_e', '123456', '1.234_6e+5'),
1092+
(',.3_e', '123456', '1.235e+5'),
1093+
(',._E', '123456', '1.234_56E+5'),
10851094

10861095
# negative zero: default behavior
10871096
('.1f', '-0', '-0.0'),
@@ -1155,6 +1164,9 @@ def test_formatting(self):
11551164
# bytes format argument
11561165
self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
11571166

1167+
# precision or fractional part separator should follow after dot
1168+
self.assertRaises(ValueError, format, Decimal(1), '.f')
1169+
11581170
def test_negative_zero_format_directed_rounding(self):
11591171
with self.decimal.localcontext() as ctx:
11601172
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)