diff options
author | John Belmonte <john@neggie.net> | 2022-04-11 23:34:18 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-11 15:34:18 +0100 |
commit | b0b836b20cb56c225874a4a39ef895f89ab2970f (patch) | |
tree | b333cd2e9ee95021e1fc3b45eee2da0b7ac0ee35 /Python | |
parent | dd207a6ac52d4bd9a71cf178fc1d5c17a6f07aff (diff) | |
download | cpython-b0b836b20cb56c225874a4a39ef895f89ab2970f.tar.gz cpython-b0b836b20cb56c225874a4a39ef895f89ab2970f.zip |
bpo-45995: add "z" format specifer to coerce negative 0 to zero (GH-30049)
Add "z" format specifier to coerce negative 0 to zero.
See https://github.com/python/cpython/issues/90153 (originally https://bugs.python.org/issue45995) for discussion.
This covers `str.format()` and f-strings. Old-style string interpolation is not supported.
Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
Diffstat (limited to 'Python')
-rw-r--r-- | Python/ast_opt.c | 1 | ||||
-rw-r--r-- | Python/formatter_unicode.c | 28 | ||||
-rw-r--r-- | Python/pystrtod.c | 22 |
3 files changed, 49 insertions, 2 deletions
diff --git a/Python/ast_opt.c b/Python/ast_opt.c index 77ed29d0cdd..b1d807bcf10 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -310,6 +310,7 @@ simple_format_arg_parse(PyObject *fmt, Py_ssize_t *ppos, case ' ': *flags |= F_BLANK; continue; case '#': *flags |= F_ALT; continue; case '0': *flags |= F_ZERO; continue; + case 'z': *flags |= F_NO_NEG_0; continue; } break; } diff --git a/Python/formatter_unicode.c b/Python/formatter_unicode.c index a1e50e20c9d..04d37c0be28 100644 --- a/Python/formatter_unicode.c +++ b/Python/formatter_unicode.c @@ -130,6 +130,7 @@ typedef struct { Py_UCS4 fill_char; Py_UCS4 align; int alternate; + int no_neg_0; Py_UCS4 sign; Py_ssize_t width; enum LocaleType thousands_separators; @@ -166,6 +167,7 @@ parse_internal_render_format_spec(PyObject *obj, format->fill_char = ' '; format->align = default_align; format->alternate = 0; + format->no_neg_0 = 0; format->sign = '\0'; format->width = -1; format->thousands_separators = LT_NO_LOCALE; @@ -193,6 +195,13 @@ parse_internal_render_format_spec(PyObject *obj, ++pos; } + /* If the next character is z, request coercion of negative 0. + Applies only to floats. */ + if (end-pos >= 1 && READ_spec(pos) == 'z') { + format->no_neg_0 = 1; + ++pos; + } + /* If the next character is #, we're in alternate mode. This only applies to integers. */ if (end-pos >= 1 && READ_spec(pos) == '#') { @@ -779,6 +788,14 @@ format_string_internal(PyObject *value, const InternalFormatSpec *format, goto done; } + /* negative 0 coercion is not allowed on strings */ + if (format->no_neg_0) { + PyErr_SetString(PyExc_ValueError, + "Negative zero coercion (z) not allowed in string format " + "specifier"); + goto done; + } + /* alternate is not allowed on strings */ if (format->alternate) { PyErr_SetString(PyExc_ValueError, @@ -872,6 +889,13 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format, "Precision not allowed in integer format specifier"); goto done; } + /* no negative zero coercion on integers */ + if (format->no_neg_0) { + PyErr_SetString(PyExc_ValueError, + "Negative zero coercion (z) not allowed in integer" + " format specifier"); + goto done; + } /* special case for character formatting */ if (format->type == 'c') { @@ -1049,6 +1073,8 @@ format_float_internal(PyObject *value, if (format->alternate) flags |= Py_DTSF_ALT; + if (format->no_neg_0) + flags |= Py_DTSF_NO_NEG_0; if (type == '\0') { /* Omitted type specifier. Behaves in the same way as repr(x) @@ -1238,6 +1264,8 @@ format_complex_internal(PyObject *value, if (format->alternate) flags |= Py_DTSF_ALT; + if (format->no_neg_0) + flags |= Py_DTSF_NO_NEG_0; if (type == '\0') { /* Omitted type specifier. Should be like str(self). */ diff --git a/Python/pystrtod.c b/Python/pystrtod.c index 1b27f0a3ad3..d77b846f040 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -916,6 +916,18 @@ char * PyOS_double_to_string(double val, (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code); _PyOS_ascii_formatd(buf, bufsize, format, val, precision); + + if (flags & Py_DTSF_NO_NEG_0 && buf[0] == '-') { + char *buf2 = buf + 1; + while (*buf2 == '0' || *buf2 == '.') { + ++buf2; + } + if (*buf2 == 0 || *buf2 == 'e') { + size_t len = buf2 - buf + strlen(buf2); + assert(buf[len] == 0); + memmove(buf, buf+1, len); + } + } } /* Add sign when requested. It's convenient (esp. when formatting @@ -995,8 +1007,8 @@ static char * format_float_short(double d, char format_code, int mode, int precision, int always_add_sign, int add_dot_0_if_integer, - int use_alt_formatting, const char * const *float_strings, - int *type) + int use_alt_formatting, int no_negative_zero, + const char * const *float_strings, int *type) { char *buf = NULL; char *p = NULL; @@ -1022,6 +1034,11 @@ format_float_short(double d, char format_code, assert(digits_end != NULL && digits_end >= digits); digits_len = digits_end - digits; + if (no_negative_zero && sign == 1 && + (digits_len == 0 || (digits_len == 1 && digits[0] == '0'))) { + sign = 0; + } + if (digits_len && !Py_ISDIGIT(digits[0])) { /* Infinities and nans here; adapt Gay's output, so convert Infinity to inf and NaN to nan, and @@ -1301,6 +1318,7 @@ char * PyOS_double_to_string(double val, flags & Py_DTSF_SIGN, flags & Py_DTSF_ADD_DOT_0, flags & Py_DTSF_ALT, + flags & Py_DTSF_NO_NEG_0, float_strings, type); } #endif // _PY_SHORT_FLOAT_REPR == 1 |