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
fix Decimal format parsing
  • Loading branch information
belm0 committed Apr 7, 2022
commit 0cbff6ac9853cbbe57b2dd7057dde30add4a1de4
4 changes: 4 additions & 0 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,7 @@ def test_formatting(self):
('z>6.1f', '-0.', 'zz-0.0'),
('z>z6.1f', '-0.', 'zzz0.0'),
('x>z6.1f', '-0.', 'xxx0.0'),
('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char

# issue 6850
('a=-7.0', '0.12345', 'aaaa0.1'),
Expand All @@ -1139,6 +1140,9 @@ def test_negative_zero_format_directed_rounding(self):
self.assertEqual(format(self.decimal.Decimal('-0.001'), 'z.2f'),
'0.00')

def test_negative_zero_bad_format(self):
self.assertRaises(ValueError, format, self.decimal.Decimal('1.23'), 'fz')

def test_n_format(self):
Decimal = self.decimal.Decimal

Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,11 +601,14 @@ def test_negative_zero(self):
self.assertEqual(f"{-0.:z>6.1f}", "zz-0.0") # test fill, esp. 'z' fill
self.assertEqual(f"{-0.:z>z6.1f}", "zzz0.0")
self.assertEqual(f"{-0.:x>z6.1f}", "xxx0.0")
self.assertEqual(f"{-0.:🖤>z6.1f}", "🖤🖤🖤0.0") # multi-byte fill char

def test_specifier_z_error(self):
error_msg = re.compile("Invalid format specifier '.*z.*'")
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:z+f}" # wrong position
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:fz}" # wrong position

error_msg = re.escape("Negative zero coercion (z) not allowed")
with self.assertRaisesRegex(ValueError, error_msg):
Expand Down
47 changes: 38 additions & 9 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -3184,8 +3184,9 @@ dotsep_as_utf8(const char *s)
}

/* copy of libmpdec _mpd_round() */
static void _mpd_round(mpd_t *result, const mpd_t *a, mpd_ssize_t prec,
const mpd_context_t *ctx, uint32_t *status)
static void
_mpd_round(mpd_t *result, const mpd_t *a, mpd_ssize_t prec,
const mpd_context_t *ctx, uint32_t *status)
{
mpd_ssize_t exp = a->exp + a->digits - prec;

Expand All @@ -3204,6 +3205,34 @@ static void _mpd_round(mpd_t *result, const mpd_t *a, mpd_ssize_t prec,
}
}

/* Locate negative zero "z" option within a UTF-8 format spec string.
* Returns pointer to "z", else NULL.
* The portion of the spec we're working with is [[fill]align][sign][z] */
static const char *
format_spec_z_search(char const *fmt, Py_ssize_t size) {
char const *pos = fmt;
char const *fmt_end = fmt + size;
/* skip over [[fill]align] (fill may be multi-byte character) */
pos += 1;
while (pos < fmt_end && *pos & 0x80) {
pos += 1;
}
if (pos < fmt_end && strchr("<>=^", *pos) != NULL) {
pos += 1;
} else {
/* fill not present-- skip over [align] */
pos = fmt;
if (pos < fmt_end && strchr("<>=^", *pos) != NULL) {
pos += 1;
}
}
/* skip over [sign] */
if (pos < fmt_end && strchr("+- ", *pos) != NULL) {
pos += 1;
}
return pos < fmt_end && *pos == 'z' ? pos : NULL;
}

static int
dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const char **valuestr)
{
Expand Down Expand Up @@ -3259,10 +3288,13 @@ dec_format(PyObject *dec, PyObject *args)
}

if (PyUnicode_Check(fmtarg)) {
fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size);
fmt = PyUnicode_AsUTF8AndSize(fmtarg, &size);
if (fmt == NULL) {
return NULL;
}
/* NOTE: If https://github.com/python/cpython/pull/29438 lands, the
* format string manipulation below can be eliminated by enhancing
* the forked mpd_parse_fmt_str(). */
if (size > 0 && fmt[0] == '\0') {
/* NUL fill character: must be replaced with a valid UTF-8 char
before calling mpd_parse_fmt_str(). */
Expand All @@ -3274,14 +3306,11 @@ dec_format(PyObject *dec, PyObject *args)
fmt_copy[0] = '_';
}
/* Strip 'z' option, which isn't understood by mpd_parse_fmt_str().
* First, skip [[fill]align], since 'fill' itself may be 'z'.
* NOTE: fmt is always null terminated by PyUnicode_AsUTF8AndSize() */
char const *fmt_offset = size >= 2 && strchr("<>=^", fmt[1]) != NULL ?
fmt + 2 : fmt;
char *z_start = strchr(fmt_offset, 'z');
if (z_start != NULL) {
char const *z_position = format_spec_z_search(fmt, size);
if (z_position != NULL) {
no_neg_0 = 1;
size_t z_index = z_start - fmt;
size_t z_index = z_position - 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) {
Expand Down