mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
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>
This commit is contained in:
parent
dd207a6ac5
commit
b0b836b20c
16 changed files with 368 additions and 43 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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). */
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue