Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a633e88
bpo-45995: add "z" format specifer to coerce negative 0 to zero
belm0 Dec 7, 2021
636da05
formatting
belm0 Dec 11, 2021
3f4085d
implementation for Decimal
belm0 Dec 13, 2021
68a049e
📜🤖 Added by blurb_it.
blurb-it[bot] Dec 14, 2021
ad32be5
consistent flag names
belm0 Dec 14, 2021
3dcaf5f
add test case for integer value with z option
belm0 Feb 3, 2022
6b9ab3b
reference pending PEP
belm0 Feb 4, 2022
e243568
Apply some formatting and doc suggestions
belm0 Mar 16, 2022
be4fda2
revise "z" option description
belm0 Mar 19, 2022
043d76a
add test cases for explicit sign option
belm0 Mar 19, 2022
104e023
revise tests for format options expected to fail on floats
belm0 Mar 19, 2022
61c64df
"float presentation" -> "floating-point presentation"
belm0 Mar 19, 2022
76d61ae
news file terminating newline
belm0 Mar 19, 2022
33fe72c
add test coverage for Decimal bugs
belm0 Mar 19, 2022
9393136
Decimal: handle 'z' fill character correctly
belm0 Mar 21, 2022
f88f7fc
Decimal: const qualifier on fmt variable
belm0 Mar 21, 2022
20c9cf1
fix rounding of 'e', 'g', and '%' presentation types for Decimal
belm0 Mar 23, 2022
bf1a891
fix Decimal directed rounding
belm0 Mar 23, 2022
3f5b392
consistency among tests
belm0 Mar 23, 2022
8d7a745
fix stack-use-after-scope sanitizer error
belm0 Mar 23, 2022
2a24e61
clarify Decimal strategy
belm0 Mar 23, 2022
0cbff6a
fix Decimal format parsing
belm0 Apr 6, 2022
8e7b51c
fix Decimal when no precision is specified
belm0 Apr 7, 2022
418ab76
fix comment typo
belm0 Apr 7, 2022
3ee6f6b
add attribution to news blurb
belm0 Apr 11, 2022
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
Prev Previous commit
Next Next commit
implementation for Decimal
  • Loading branch information
belm0 committed Mar 19, 2022
commit 3f4085d88b0b8e073ea83b41fc050aa028701f9f
9 changes: 7 additions & 2 deletions Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3795,6 +3795,10 @@ def __format__(self, specifier, context=None, _localeconv=None):
# represented in fixed point; rescale them to 0e0.
if not self and self._exp > 0 and spec['type'] in 'fF%':
self = self._rescale(0, rounding)
if not self and spec['coerce_neg_0'] and self._sign:
adjusted_sign = 0
else:
adjusted_sign = self._sign

# figure out placement of the decimal point
leftdigits = self._exp + len(self._int)
Expand Down Expand Up @@ -3825,7 +3829,7 @@ def __format__(self, specifier, context=None, _localeconv=None):

# done with the decimal-specific stuff; hand over the rest
# of the formatting to the _format_number function
return _format_number(self._sign, intpart, fracpart, exp, spec)
return _format_number(adjusted_sign, intpart, fracpart, exp, spec)

def _dec_from_triple(sign, coefficient, exponent, special=False):
"""Create a decimal instance directly, without any validation,
Expand Down Expand Up @@ -6143,14 +6147,15 @@ def _convert_for_comparison(self, other, equality_op=False):
#
# A format specifier for Decimal looks like:
#
# [[fill]align][sign][#][0][minimumwidth][,][.precision][type]
# [[fill]align][sign][z][#][0][minimumwidth][,][.precision][type]

_parse_format_specifier_regex = re.compile(r"""\A
(?:
(?P<fill>.)?
(?P<align>[<>=^])
)?
(?P<sign>[-+ ])?
(?P<coerce_neg_0>z)?
(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,39 @@ def test_formatting(self):
(',e', '123456', '1.23456e+5'),
(',E', '123456', '1.23456E+5'),

# negative zero: default behavior
Comment thread
belm0 marked this conversation as resolved.
Outdated
('.1f', '-0', '-0.0'),
('.1f', '-.0', '-0.0'),
('.1f', '-.01', '-0.0'),

# negative zero: z option
('z.1f', '0.', '0.0'),
('z6.1f', '0.', ' 0.0'),
('z6.1f', '-1.', ' -1.0'),
('z.1f', '-0.', '0.0'),
('z.1f', '.01', '0.0'),
('z.1f', '-.01', '0.0'),
('z.2f', '0.', '0.00'),
('z.2f', '-0.', '0.00'),
('z.2f', '.001', '0.00'),
('z.2f', '-.001', '0.00'),

('z.1e', '0.', '0.0e+1'),
('z.1e', '-0.', '0.0e+1'),
('z.1E', '0.', '0.0E+1'),
('z.1E', '-0.', '0.0E+1'),

('z.1f', '-00000.000001', '0.0'),
('z.1f', '-00000.', '0.0'),
('z.1f', '-.0000000000', '0.0'),

('z.2f', '-00000.000001', '0.00'),
('z.2f', '-00000.', '0.00'),
('z.2f', '-.0000000000', '0.00'),

('z.1f', '.09', '0.1'),
('z.1f', '-.09', '-0.1'),

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

Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from decimal import Decimal
from test.support import verbose, TestFailed
import locale
import sys
Expand Down Expand Up @@ -552,7 +551,6 @@ def test_negative_zero(self):
self.assertEqual(f"{-0.:.1f}", "-0.0")
self.assertEqual(f"{-.01:.1f}", "-0.0")
self.assertEqual(f"{-0:.1f}", "0.0") # integers do not distinguish -0
self.assertEqual(f"{Decimal('-0'):.1f}", "-0.0")

## z sign option
self.assertEqual(f"{0.:z.1f}", "0.0")
Expand Down
36 changes: 32 additions & 4 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -3221,9 +3221,11 @@ dec_format(PyObject *dec, PyObject *args)
PyObject *context;
mpd_spec_t spec;
char *fmt;
char *fmt_copy = NULL;
char *decstring = NULL;
uint32_t status = 0;
int replace_fillchar = 0;
int coerce_neg_0 = 0;
Py_ssize_t size;


Expand All @@ -3241,11 +3243,24 @@ dec_format(PyObject *dec, PyObject *args)
/* NUL fill character: must be replaced with a valid UTF-8 char
before calling mpd_parse_fmt_str(). */
replace_fillchar = 1;
fmt = dec_strdup(fmt, size);
if (fmt == NULL) {
fmt = fmt_copy = dec_strdup(fmt, size);
if (fmt_copy == NULL) {
return NULL;
}
fmt[0] = '_';
fmt_copy[0] = '_';
}
char *z_start = strchr(fmt, 'z');
Comment thread
belm0 marked this conversation as resolved.
Outdated
if (z_start != NULL) {
coerce_neg_0 = 1;
size_t z_index = z_start - fmt;
if (fmt_copy == NULL) {
fmt = fmt_copy = dec_strdup(fmt, size);
Comment thread
mdickinson marked this conversation as resolved.
Outdated
if (fmt_copy == NULL) {
return NULL;
}
}
memmove(fmt_copy + z_index, fmt_copy + z_index + 1, size - z_index);
Comment thread
belm0 marked this conversation as resolved.
Outdated
size -= 1;
}
}
else {
Expand Down Expand Up @@ -3311,6 +3326,19 @@ dec_format(PyObject *dec, PyObject *args)
}
}

if (coerce_neg_0 && mpd_isnegative(MPD(dec)) && !mpd_isspecial(MPD(dec))) {
/* round into a temporary and clear sign if result is zero */
mpd_uint_t dt[MPD_MINALLOC_MAX];
mpd_t tmp = {MPD_STATIC|MPD_STATIC_DATA,0,0,0,MPD_MINALLOC_MAX,dt};
mpd_qrescale(&tmp, MPD(dec), -spec.prec, CTX(context), &status);
Comment thread
belm0 marked this conversation as resolved.
Outdated
if (status & MPD_Errors) {
PyErr_SetString(PyExc_ValueError, "unexpected error when rounding");
goto finish;
}
if (mpd_iszero(&tmp)) {
mpd_set_positive(MPD(dec));
}
}

decstring = mpd_qformat_spec(MPD(dec), &spec, CTX(context), &status);
if (decstring == NULL) {
Expand All @@ -3335,7 +3363,7 @@ dec_format(PyObject *dec, PyObject *args)
Py_XDECREF(grouping);
Py_XDECREF(sep);
Py_XDECREF(dot);
if (replace_fillchar) PyMem_Free(fmt);
if (fmt_copy) PyMem_Free(fmt_copy);
if (decstring) mpd_free(decstring);
return result;
}
Expand Down