Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-45708: Support underscore separators when formatting Decimal objects #29438

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/library/decimal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ Decimal objects
Underscores are allowed for grouping, as with integral and floating-point
literals in code.

.. versionchanged:: 3.11
The underscore grouping option in the :ref:`formatting mini-language
<formatspec>` is now supported for :class:`Decimal` objects:
``f"{Decimal(1234567):_}"`` gives ``'1_234_567'``.

Decimal floating point objects share many properties with the other built-in
numeric types such as :class:`float` and :class:`int`. All of the usual math
operations and special methods apply. Likewise, decimal objects can be
Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ New Modules
Improved Modules
================

decimal
-------

* Formatting of :class:`~decimal.Decimal` objects now supports underscores
for grouping, as outlined in :PEP:`515`. For example,
``f"{Decimal(1234567):_}"`` gives ``'1_234_567'``. Previously, only commas
were supported for grouping. (Contributed by Mark Dickinson in
:issue:`45708`.)

fractions
---------

Expand Down
2 changes: 1 addition & 1 deletion Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -6154,7 +6154,7 @@ def _convert_for_comparison(self, other, equality_op=False):
(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?
(?P<thousands_sep>,)?
(?P<thousands_sep>[_,])?
(?:\.(?P<precision>0|(?!0)\d+))?
(?P<type>[eEfFgGn%])?
\Z
Expand Down
34 changes: 33 additions & 1 deletion Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ def test_formatting(self):
('\x00>10', '1.2345', '\x00\x00\x00\x001.2345'),
('\x00<10', '1.2345', '1.2345\x00\x00\x00\x00'),

# thousands separator
# thousands separator: ','
(',', '1234567', '1,234,567'),
(',', '123456', '123,456'),
(',', '12345', '12,345'),
Expand Down Expand Up @@ -1082,6 +1082,38 @@ def test_formatting(self):
(',e', '123456', '1.23456e+5'),
(',E', '123456', '1.23456E+5'),

# thousands separator: '_'
('_', '1234567', '1_234_567'),
('_', '123456', '123_456'),
('_', '12345', '12_345'),
('_', '1234', '1_234'),
('_', '123', '123'),
('_', '12', '12'),
('_', '1', '1'),
('_', '0', '0'),
('_', '-1234567', '-1_234_567'),
('_', '-123456', '-123_456'),
('7_', '123456', '123_456'),
('8_', '123456', ' 123_456'),
('08_', '123456', '0_123_456'), # special case: extra 0 needed
('+08_', '123456', '+123_456'), # but not if there's a sign
(' 08_', '123456', ' 123_456'),
('08_', '-123456', '-123_456'),
('+09_', '123456', '+0_123_456'),
# ... with fractional part...
('07_', '1234.56', '1_234.56'),
('08_', '1234.56', '1_234.56'),
('09_', '1234.56', '01_234.56'),
('010_', '1234.56', '001_234.56'),
('011_', '1234.56', '0_001_234.56'),
('012_', '1234.56', '0_001_234.56'),
('08_.1f', '1234.5', '01_234.5'),
# no thousands separators in fraction part
('_', '1.23456789', '1.23456789'),
('_%', '123.456789', '12_345.6789%'),
('_e', '123456', '1.23456e+5'),
('_E', '123456', '1.23456E+5'),

# issue 6850
('a=-7.0', '0.12345', 'aaaa0.1'),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support underscores in :class:`~decimal.Decimal` object formatting: for
example, ``f"{Decimal(1234567):_}"`` now gives ``'1_234_567'``. (Previously,
this gave an "invalid format string" :exc:`ValueError`.)
6 changes: 6 additions & 0 deletions Modules/_decimal/libmpdec/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,12 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps)
spec->grouping = "\003\003";
cp++;
}
else if (*cp == '_') {
spec->dot = ".";
spec->sep = "_";
spec->grouping = "\003\003";
cp++;
}

/* fraction digits or significant digits */
if (*cp == '.') {
Expand Down