From d49d67970ac45326a524e3112991f3570f624104 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Thu, 1 Feb 2024 12:00:58 -0800 Subject: [PATCH 01/12] add decimal tests --- Lib/test/test_decimal.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 1423bc61c7f690..a3f8c2ceaf8ff7 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1111,6 +1111,12 @@ def test_formatting(self): ('x>z6.1f', '-0.', 'xxx0.0'), ('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char + # issue 114563 ('z' format on F type in cdecimal) + ('z3,.10F', '-6.24E-323', '0.0000000000'), + + # issue 91060 ('#' format in cdecimal) + ('#', '0', '0.'), + # issue 6850 ('a=-7.0', '0.12345', 'aaaa0.1'), From b52600420b65a91681359b55e81820d7448de2f0 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Thu, 1 Feb 2024 12:11:23 -0800 Subject: [PATCH 02/12] revert 'z' format support in C decimal --- Modules/_decimal/_decimal.c | 126 ++---------------------------------- 1 file changed, 7 insertions(+), 119 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 127f5f2887d4cd..d543c774fb2994 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3336,56 +3336,6 @@ dotsep_as_utf8(const char *s) return utf8; } -/* 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) -{ - mpd_ssize_t exp = a->exp + a->digits - prec; - - if (prec <= 0) { - mpd_seterror(result, MPD_Invalid_operation, status); - return; - } - if (mpd_isspecial(a) || mpd_iszero(a)) { - mpd_qcopy(result, a, status); - return; - } - - mpd_qrescale_fmt(result, a, exp, ctx, status); - if (result->digits > prec) { - mpd_qrescale_fmt(result, result, exp+1, ctx, status); - } -} - -/* 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) { @@ -3423,16 +3373,11 @@ dec_format(PyObject *dec, PyObject *args) PyObject *fmtarg; PyObject *context; mpd_spec_t spec; - char const *fmt; - char *fmt_copy = NULL; + char *fmt; char *decstring = NULL; uint32_t status = 0; int replace_fillchar = 0; - int no_neg_0 = 0; Py_ssize_t size; - mpd_t *mpd = MPD(dec); - mpd_uint_t dt[MPD_MINALLOC_MAX]; - mpd_t tmp = {MPD_STATIC|MPD_STATIC_DATA,0,0,0,MPD_MINALLOC_MAX,dt}; decimal_state *state = get_module_state_by_def(Py_TYPE(dec)); @@ -3442,7 +3387,7 @@ dec_format(PyObject *dec, PyObject *args) } if (PyUnicode_Check(fmtarg)) { - fmt = PyUnicode_AsUTF8AndSize(fmtarg, &size); + fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size); if (fmt == NULL) { return NULL; } @@ -3454,35 +3399,15 @@ dec_format(PyObject *dec, PyObject *args) } } - /* 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(). */ replace_fillchar = 1; - fmt = fmt_copy = dec_strdup(fmt, size); - if (fmt_copy == NULL) { + fmt = dec_strdup(fmt, size); + if (fmt == NULL) { return NULL; } - fmt_copy[0] = '_'; - } - /* Strip 'z' option, which isn't understood by mpd_parse_fmt_str(). - * NOTE: fmt is always null terminated by PyUnicode_AsUTF8AndSize() */ - char const *z_position = format_spec_z_search(fmt, size); - if (z_position != NULL) { - no_neg_0 = 1; - size_t z_index = z_position - fmt; - if (fmt_copy == NULL) { - fmt = fmt_copy = dec_strdup(fmt, size); - if (fmt_copy == NULL) { - return NULL; - } - } - /* Shift characters (including null terminator) left, - overwriting the 'z' option. */ - memmove(fmt_copy + z_index, fmt_copy + z_index + 1, size - z_index); - size -= 1; + fmt[0] = '_'; } } else { @@ -3548,45 +3473,8 @@ dec_format(PyObject *dec, PyObject *args) } } - if (no_neg_0 && mpd_isnegative(mpd) && !mpd_isspecial(mpd)) { - /* Round into a temporary (carefully mirroring the rounding - of mpd_qformat_spec()), and check if the result is negative zero. - If so, clear the sign and format the resulting positive zero. */ - mpd_ssize_t prec; - mpd_qcopy(&tmp, mpd, &status); - if (spec.prec >= 0) { - switch (spec.type) { - case 'f': - mpd_qrescale(&tmp, &tmp, -spec.prec, CTX(context), &status); - break; - case '%': - tmp.exp += 2; - mpd_qrescale(&tmp, &tmp, -spec.prec, CTX(context), &status); - break; - case 'g': - prec = (spec.prec == 0) ? 1 : spec.prec; - if (tmp.digits > prec) { - _mpd_round(&tmp, &tmp, prec, CTX(context), &status); - } - break; - case 'e': - if (!mpd_iszero(&tmp)) { - _mpd_round(&tmp, &tmp, spec.prec+1, CTX(context), &status); - } - break; - } - } - if (status & MPD_Errors) { - PyErr_SetString(PyExc_ValueError, "unexpected error when rounding"); - goto finish; - } - if (mpd_iszero(&tmp)) { - mpd_set_positive(&tmp); - mpd = &tmp; - } - } - decstring = mpd_qformat_spec(mpd, &spec, CTX(context), &status); + decstring = mpd_qformat_spec(MPD(dec), &spec, CTX(context), &status); if (decstring == NULL) { if (status & MPD_Malloc_error) { PyErr_NoMemory(); @@ -3609,7 +3497,7 @@ dec_format(PyObject *dec, PyObject *args) Py_XDECREF(grouping); Py_XDECREF(sep); Py_XDECREF(dot); - if (fmt_copy) PyMem_Free(fmt_copy); + if (replace_fillchar) PyMem_Free(fmt); if (decstring) mpd_free(decstring); return result; } From 6ac60d7a08b36d476ecb3fdfcd6284f2a7706477 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Thu, 1 Feb 2024 12:13:23 -0800 Subject: [PATCH 03/12] gh-114563: C decimal falls back to pydecimal for unsupported format strings immediate merits: * eliminate complex workarounds for 'z' format support (NOTE: mpdecimal recently added 'z' support, so this becomes efficient in the long term.) * fix 'z' format memory leak * fix 'z' format applied to 'F' * fix missing '#' format support Suggested and prototyped by Stefan Krah. Fixes gh-114563, gh-91060 --- Modules/_decimal/_decimal.c | 65 +++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index d543c774fb2994..355879dadf98cd 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -82,6 +82,9 @@ typedef struct { /* Convert rationals for comparison */ PyObject *Rational; + /* Invariant: NULL or pointer to _pydecimal.Decimal */ + PyObject *PyDecimal; + PyObject *SignalTuple; struct DecCondMap *signal_map; @@ -3361,6 +3364,55 @@ dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const return 0; } +/* + * Fallback _pydecimal formatting for new format specifiers that mpdecimal does + * not yet support. As documented, libmpdec follows the PEP-3101 format language: + * https://www.bytereef.org/mpdecimal/doc/libmpdec/assign-convert.html#to-string + */ +static PyObject * +pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) +{ + PyObject *result; + PyObject *pydec; + PyObject *u; + + if (state->PyDecimal == NULL) { + PyObject *obj = PyImport_ImportModule("_pydecimal"); + if (obj == NULL) { + return NULL; + } + + state->PyDecimal = PyObject_GetAttrString(obj, "Decimal"); + Py_DECREF(obj); + + if (state->PyDecimal == NULL) { + return NULL; + } + } + + u = dec_str(dec); + if (u == NULL) { + return NULL; + } + + pydec = PyObject_CallFunctionObjArgs(state->PyDecimal, u, NULL); + Py_DECREF(u); + if (pydec == NULL) { + return NULL; + } + + result = PyObject_CallMethod(pydec, "__format__", "(O)", fmt); + Py_DECREF(pydec); + + if (result == NULL) { + /* Do not confuse users with the _pydecimal exception */ + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, "invalid format string"); + } + + return result; +} + /* Formatted representation of a PyDecObject. */ static PyObject * dec_format(PyObject *dec, PyObject *args) @@ -3417,10 +3469,13 @@ dec_format(PyObject *dec, PyObject *args) } if (!mpd_parse_fmt_str(&spec, fmt, CtxCaps(context))) { - PyErr_SetString(PyExc_ValueError, - "invalid format string"); - goto finish; + if (replace_fillchar) { + PyMem_Free(fmt); + } + + return pydec_format(dec, fmtarg, state); } + if (replace_fillchar) { /* In order to avoid clobbering parts of UTF-8 thousands separators or decimal points when the substitution is reversed later, the actual @@ -5875,6 +5930,9 @@ _decimal_exec(PyObject *m) Py_CLEAR(collections_abc); Py_CLEAR(MutableMapping); + /* For format specifiers not yet supported by libmpdec */ + state->PyDecimal = NULL; + /* Add types to the module */ CHECK_INT(PyModule_AddType(m, state->PyDec_Type)); CHECK_INT(PyModule_AddType(m, state->PyDecContext_Type)); @@ -6080,6 +6138,7 @@ decimal_clear(PyObject *module) Py_CLEAR(state->extended_context_template); Py_CLEAR(state->Rational); Py_CLEAR(state->SignalTuple); + Py_CLEAR(state->PyDecimal); PyMem_Free(state->signal_map); PyMem_Free(state->cond_map); From d37b5b81c582a8964fe83683e88660e9c4d4017b Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sun, 11 Feb 2024 02:41:17 +0900 Subject: [PATCH 04/12] Apply suggestions from code review Co-authored-by: Serhiy Storchaka --- Lib/test/test_decimal.py | 1 + Modules/_decimal/_decimal.c | 13 +++---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index a3f8c2ceaf8ff7..e3194b0dcb37f7 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1110,6 +1110,7 @@ def test_formatting(self): ('z>z6.1f', '-0.', 'zzz0.0'), ('x>z6.1f', '-0.', 'xxx0.0'), ('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char + ('\0>z6.1f', '-0.', '\0\0\00.0'), # null fill char # issue 114563 ('z' format on F type in cdecimal) ('z3,.10F', '-6.24E-323', '0.0000000000'), diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 355879dadf98cd..fa66bd364d4998 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3377,14 +3377,7 @@ pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) PyObject *u; if (state->PyDecimal == NULL) { - PyObject *obj = PyImport_ImportModule("_pydecimal"); - if (obj == NULL) { - return NULL; - } - - state->PyDecimal = PyObject_GetAttrString(obj, "Decimal"); - Py_DECREF(obj); - + state->PyDecimal = _PyImport_GetModuleAttrString("_pydecimal", "Decimal"); if (state->PyDecimal == NULL) { return NULL; } @@ -3395,13 +3388,13 @@ pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) return NULL; } - pydec = PyObject_CallFunctionObjArgs(state->PyDecimal, u, NULL); + pydec = PyObject_CallOneArg(state->PyDecimal, u); Py_DECREF(u); if (pydec == NULL) { return NULL; } - result = PyObject_CallMethod(pydec, "__format__", "(O)", fmt); + result = PyObject_Format(pydec, fmt); Py_DECREF(pydec); if (result == NULL) { From d2b68934089686c378a8296031e77afeb3d7393a Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sat, 10 Feb 2024 10:05:30 -0800 Subject: [PATCH 05/12] disable failing null fill test --- Lib/test/test_decimal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index e3194b0dcb37f7..fbb02ee482bd96 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1110,7 +1110,9 @@ def test_formatting(self): ('z>z6.1f', '-0.', 'zzz0.0'), ('x>z6.1f', '-0.', 'xxx0.0'), ('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char - ('\0>z6.1f', '-0.', '\0\0\00.0'), # null fill char + # TODO: fix this null fill char case (and confirm mpdecimal impl.) + # output is ".0", expected is "0.0" + #('\0>z6.1f', '-0.', '\0\0\00.0'), # null fill char # issue 114563 ('z' format on F type in cdecimal) ('z3,.10F', '-6.24E-323', '0.0000000000'), From feb746947d56c23fdceed389a21d35629419212f Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sat, 10 Feb 2024 10:09:47 -0800 Subject: [PATCH 06/12] only override message string on ValueError --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index fa66bd364d4998..83cc1ac122de1d 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3397,7 +3397,7 @@ pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) result = PyObject_Format(pydec, fmt); Py_DECREF(pydec); - if (result == NULL) { + if (result == NULL && PyErr_ExceptionMatches(PyExc_ValueError)) { /* Do not confuse users with the _pydecimal exception */ PyErr_Clear(); PyErr_SetString(PyExc_ValueError, "invalid format string"); From 5803d9c962aa0971ba3823ec82b699d1e0fa4da3 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sat, 10 Feb 2024 10:14:18 -0800 Subject: [PATCH 07/12] corrected null fill char test --- Lib/test/test_decimal.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index fbb02ee482bd96..950500a3786b56 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1110,9 +1110,7 @@ def test_formatting(self): ('z>z6.1f', '-0.', 'zzz0.0'), ('x>z6.1f', '-0.', 'xxx0.0'), ('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char - # TODO: fix this null fill char case (and confirm mpdecimal impl.) - # output is ".0", expected is "0.0" - #('\0>z6.1f', '-0.', '\0\0\00.0'), # null fill char + ('\x00>z6.1f', '-0.', '\x00\x00\x000.0'), # null fill char # issue 114563 ('z' format on F type in cdecimal) ('z3,.10F', '-6.24E-323', '0.0000000000'), From dcee58f3f3d5ada666954499ddc455370ddc4705 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sat, 10 Feb 2024 16:26:23 -0800 Subject: [PATCH 08/12] honor round and capitals context in fallback --- Lib/_pydecimal.py | 6 ++++++ Modules/_decimal/_decimal.c | 12 +++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 2692f2fcba45bf..d3cc4e06ae812a 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -3841,6 +3841,12 @@ def __format__(self, specifier, context=None, _localeconv=None): # of the formatting to the _format_number function return _format_number(adjusted_sign, intpart, fracpart, exp, spec) + def _fallback_format(self, specifier, rounding, caps): + """returns `__format__(specifier)` using a temporary context""" + c = Context(rounding=rounding, capitals=caps) + return self.__format__(specifier, c) + + def _dec_from_triple(sign, coefficient, exponent, special=False): """Create a decimal instance directly, without any validation, normalization (e.g. removal of leading zeros) or argument diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 83cc1ac122de1d..7475972eb9528c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3370,10 +3370,12 @@ dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const * https://www.bytereef.org/mpdecimal/doc/libmpdec/assign-convert.html#to-string */ static PyObject * -pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) +pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *state) { PyObject *result; PyObject *pydec; + PyObject *rounding; + PyObject *caps; PyObject *u; if (state->PyDecimal == NULL) { @@ -3394,7 +3396,11 @@ pydec_format(PyObject *dec, PyObject *fmt, decimal_state *state) return NULL; } - result = PyObject_Format(pydec, fmt); + rounding = context_getround(context, NULL); + caps = context_getcapitals(context, NULL); + result = PyObject_CallMethod(pydec, "_fallback_format", "(OOO)", fmt, rounding, caps); + Py_DECREF(rounding); + Py_DECREF(caps); Py_DECREF(pydec); if (result == NULL && PyErr_ExceptionMatches(PyExc_ValueError)) { @@ -3466,7 +3472,7 @@ dec_format(PyObject *dec, PyObject *args) PyMem_Free(fmt); } - return pydec_format(dec, fmtarg, state); + return pydec_format(dec, context, fmtarg, state); } if (replace_fillchar) { From 7a2ba4bf2acccdec766c4f7e2633c5df8921c018 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sat, 10 Feb 2024 17:14:45 -0800 Subject: [PATCH 09/12] format fallback context tests --- Lib/test/test_decimal.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 950500a3786b56..f23ea8af0c8772 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -5733,6 +5733,21 @@ def test_c_signaldict_segfault(self): with self.assertRaisesRegex(ValueError, err_msg): sd.copy() + def test_format_fallback_capitals(self): + # Fallback to _pydecimal formatting (triggered by `#` format which + # is unsupported by mpdecimal) should honor the current context. + x = C.Decimal('6.09e+23') + self.assertEqual(format(x, '#'), '6.09E+23') + with C.localcontext(capitals=0): + self.assertEqual(format(x, '#'), '6.09e+23') + + def test_format_fallback_rounding(self): + y = C.Decimal('6.09') + self.assertEqual(format(y, '#.1f'), '6.1') + with C.localcontext(rounding=C.ROUND_DOWN): + self.assertEqual(format(y, '#.1f'), '6.0') + + @requires_docstrings @requires_cdecimal class SignatureTest(unittest.TestCase): From ab4f2d7fea366507294a143fefb45c656ff81780 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sun, 11 Feb 2024 07:59:54 -0800 Subject: [PATCH 10/12] pass context directly to __format__() --- Lib/_pydecimal.py | 6 ------ Modules/_decimal/_decimal.c | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index d3cc4e06ae812a..2692f2fcba45bf 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -3841,12 +3841,6 @@ def __format__(self, specifier, context=None, _localeconv=None): # of the formatting to the _format_number function return _format_number(adjusted_sign, intpart, fracpart, exp, spec) - def _fallback_format(self, specifier, rounding, caps): - """returns `__format__(specifier)` using a temporary context""" - c = Context(rounding=rounding, capitals=caps) - return self.__format__(specifier, c) - - def _dec_from_triple(sign, coefficient, exponent, special=False): """Create a decimal instance directly, without any validation, normalization (e.g. removal of leading zeros) or argument diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 7475972eb9528c..5b053c73e20bc9 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3374,8 +3374,6 @@ pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *sta { PyObject *result; PyObject *pydec; - PyObject *rounding; - PyObject *caps; PyObject *u; if (state->PyDecimal == NULL) { @@ -3396,11 +3394,7 @@ pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *sta return NULL; } - rounding = context_getround(context, NULL); - caps = context_getcapitals(context, NULL); - result = PyObject_CallMethod(pydec, "_fallback_format", "(OOO)", fmt, rounding, caps); - Py_DECREF(rounding); - Py_DECREF(caps); + result = PyObject_CallMethod(pydec, "__format__", "(OO)", fmt, context); Py_DECREF(pydec); if (result == NULL && PyErr_ExceptionMatches(PyExc_ValueError)) { From 3b1b8f4ee3037319d58599b6a43b6ef50a261191 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:23:41 +0000 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst new file mode 100644 index 00000000000000..09941d9f0d10bd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst @@ -0,0 +1,4 @@ +Fix several `format()` bugs when using the C implementation of `Decimal`: +* memory leak in some rare cases when using the `z` format option (coerce negative 0) +* incorrect output when applying the `z` format option to type `F` (fixed-point with capital `NAN` / `INF`) +* incorrect output when applying the `#` format option (alternate form) From 06e371cf9241b0b236e31f5d0937db8e7feb14fe Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sun, 11 Feb 2024 12:30:25 -0800 Subject: [PATCH 12/12] fix NEWS formatting --- .../2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst index 09941d9f0d10bd..013b6db8e6dbd7 100644 --- a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst +++ b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst @@ -1,4 +1,4 @@ -Fix several `format()` bugs when using the C implementation of `Decimal`: -* memory leak in some rare cases when using the `z` format option (coerce negative 0) -* incorrect output when applying the `z` format option to type `F` (fixed-point with capital `NAN` / `INF`) -* incorrect output when applying the `#` format option (alternate form) +Fix several :func:`format()` bugs when using the C implementation of :class:`~decimal.Decimal`: +* memory leak in some rare cases when using the ``z`` format option (coerce negative 0) +* incorrect output when applying the ``z`` format option to type ``F`` (fixed-point with capital ``NAN`` / ``INF``) +* incorrect output when applying the ``#`` format option (alternate form)