mirror of
https://github.com/python/cpython.git
synced 2025-11-25 21:11:09 +00:00
Issue 2264: empty float presentation type needs to have at least one digit past the decimal point.
Added "Z" format_char to PyOS_ascii_formatd to support empty float presentation type. Renamed buf_size in PyOS_ascii_formatd to more accurately reflect it's meaning. Modified format.__float__ to use the new "Z" format as the default. Added test cases.
This commit is contained in:
parent
43da35de7b
commit
8113ca63b9
3 changed files with 76 additions and 18 deletions
|
|
@ -521,6 +521,15 @@ class TypesTests(unittest.TestCase):
|
||||||
test(1.1234e20, 'e', '1.123400e+20')
|
test(1.1234e20, 'e', '1.123400e+20')
|
||||||
test(1.1234e20, 'E', '1.123400E+20')
|
test(1.1234e20, 'E', '1.123400E+20')
|
||||||
|
|
||||||
|
# No format code means use g, but must have a decimal
|
||||||
|
# and a number after the decimal. This is tricky, because
|
||||||
|
# a totaly empty format specifier means something else.
|
||||||
|
# So, just use a sign flag
|
||||||
|
test(1e200, '+g', '+1e+200')
|
||||||
|
test(1e200, '+', '+1.0e+200')
|
||||||
|
test(1.1e200, '+g', '+1.1e+200')
|
||||||
|
test(1.1e200, '+', '+1.1e+200')
|
||||||
|
|
||||||
# % formatting
|
# % formatting
|
||||||
test(-1.0, '%', '-100.000000%')
|
test(-1.0, '%', '-100.000000%')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -925,11 +925,16 @@ FORMAT_FLOAT(PyObject *value, PyObject *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* parse the format_spec */
|
/* parse the format_spec */
|
||||||
if (!parse_internal_render_format_spec(format_spec, &format, 'g'))
|
if (!parse_internal_render_format_spec(format_spec, &format, '\0'))
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* type conversion? */
|
/* type conversion? */
|
||||||
switch (format.type) {
|
switch (format.type) {
|
||||||
|
case '\0':
|
||||||
|
/* 'Z' means like 'g', but with at least one decimal. See
|
||||||
|
PyOS_ascii_formatd */
|
||||||
|
format.type = 'Z';
|
||||||
|
/* Deliberate fall through to the next case statement */
|
||||||
case 'e':
|
case 'e':
|
||||||
case 'E':
|
case 'E':
|
||||||
case 'f':
|
case 'f':
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ as necessary to represent the exponent.
|
||||||
/**
|
/**
|
||||||
* PyOS_ascii_formatd:
|
* PyOS_ascii_formatd:
|
||||||
* @buffer: A buffer to place the resulting string in
|
* @buffer: A buffer to place the resulting string in
|
||||||
* @buf_len: The length of the buffer.
|
* @buf_size: The length of the buffer.
|
||||||
* @format: The printf()-style format to use for the
|
* @format: The printf()-style format to use for the
|
||||||
* code to use for converting.
|
* code to use for converting.
|
||||||
* @d: The #gdouble to convert
|
* @d: The #gdouble to convert
|
||||||
|
|
@ -209,12 +209,14 @@ as necessary to represent the exponent.
|
||||||
* specifiers are 'e', 'E', 'f', 'F', 'g', 'G', and 'n'.
|
* specifiers are 'e', 'E', 'f', 'F', 'g', 'G', and 'n'.
|
||||||
*
|
*
|
||||||
* 'n' is the same as 'g', except it uses the current locale.
|
* 'n' is the same as 'g', except it uses the current locale.
|
||||||
|
* 'Z' is the same as 'g', except it always has a decimal and
|
||||||
|
* at least one digit after the decimal.
|
||||||
*
|
*
|
||||||
* Return value: The pointer to the buffer with the converted string.
|
* Return value: The pointer to the buffer with the converted string.
|
||||||
**/
|
**/
|
||||||
char *
|
char *
|
||||||
PyOS_ascii_formatd(char *buffer,
|
PyOS_ascii_formatd(char *buffer,
|
||||||
size_t buf_len,
|
size_t buf_size,
|
||||||
const char *format,
|
const char *format,
|
||||||
double d)
|
double d)
|
||||||
{
|
{
|
||||||
|
|
@ -227,20 +229,13 @@ PyOS_ascii_formatd(char *buffer,
|
||||||
can't modify it directly. FLOAT_FORMATBUFLEN should be longer than
|
can't modify it directly. FLOAT_FORMATBUFLEN should be longer than
|
||||||
we ever need this to be. There's an upcoming check to ensure it's
|
we ever need this to be. There's an upcoming check to ensure it's
|
||||||
big enough. */
|
big enough. */
|
||||||
|
/* Issue 2264: code 'Z' requires copying the format. 'Z' is 'g', but
|
||||||
|
also with at least one character past the decimal. */
|
||||||
char tmp_format[FLOAT_FORMATBUFLEN];
|
char tmp_format[FLOAT_FORMATBUFLEN];
|
||||||
|
|
||||||
/* g_return_val_if_fail (buffer != NULL, NULL); */
|
|
||||||
/* g_return_val_if_fail (format[0] == '%', NULL); */
|
|
||||||
/* g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL); */
|
|
||||||
|
|
||||||
/* The last character in the format string must be the format char */
|
/* The last character in the format string must be the format char */
|
||||||
format_char = format[format_len - 1];
|
format_char = format[format_len - 1];
|
||||||
|
|
||||||
/* g_return_val_if_fail (format_char == 'e' || format_char == 'E' || */
|
|
||||||
/* format_char == 'f' || format_char == 'F' || */
|
|
||||||
/* format_char == 'g' || format_char == 'G', */
|
|
||||||
/* NULL); */
|
|
||||||
|
|
||||||
if (format[0] != '%')
|
if (format[0] != '%')
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
|
@ -251,15 +246,20 @@ PyOS_ascii_formatd(char *buffer,
|
||||||
if (strpbrk(format + 1, "'l%"))
|
if (strpbrk(format + 1, "'l%"))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
/* Also curious about this function is that it accepts format strings
|
||||||
|
like "%xg", which are invalid for floats. In general, the
|
||||||
|
interface to this function is not very good, but changing it is
|
||||||
|
difficult because it's a public API. */
|
||||||
|
|
||||||
if (!(format_char == 'e' || format_char == 'E' ||
|
if (!(format_char == 'e' || format_char == 'E' ||
|
||||||
format_char == 'f' || format_char == 'F' ||
|
format_char == 'f' || format_char == 'F' ||
|
||||||
format_char == 'g' || format_char == 'G' ||
|
format_char == 'g' || format_char == 'G' ||
|
||||||
format_char == 'n'))
|
format_char == 'n' || format_char == 'Z'))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Map 'n' format_char to 'g', by copying the format string and
|
/* Map 'n' or 'Z' format_char to 'g', by copying the format string and
|
||||||
replacing the final 'n' with a 'g' */
|
replacing the final char with a 'g' */
|
||||||
if (format_char == 'n') {
|
if (format_char == 'n' || format_char == 'Z') {
|
||||||
if (format_len + 1 >= sizeof(tmp_format)) {
|
if (format_len + 1 >= sizeof(tmp_format)) {
|
||||||
/* The format won't fit in our copy. Error out. In
|
/* The format won't fit in our copy. Error out. In
|
||||||
practice, this will never happen and will be detected
|
practice, this will never happen and will be detected
|
||||||
|
|
@ -271,8 +271,9 @@ PyOS_ascii_formatd(char *buffer,
|
||||||
format = tmp_format;
|
format = tmp_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Have PyOS_snprintf do the hard work */
|
/* Have PyOS_snprintf do the hard work */
|
||||||
PyOS_snprintf(buffer, buf_len, format, d);
|
PyOS_snprintf(buffer, buf_size, format, d);
|
||||||
|
|
||||||
/* Get the current local, and find the decimal point character (or
|
/* Get the current local, and find the decimal point character (or
|
||||||
string?). Convert that string back to a dot. Do not do this if
|
string?). Convert that string back to a dot. Do not do this if
|
||||||
|
|
@ -360,7 +361,7 @@ PyOS_ascii_formatd(char *buffer,
|
||||||
until there are 2, if there's enough room */
|
until there are 2, if there's enough room */
|
||||||
int zeros = MIN_EXPONENT_DIGITS - exponent_digit_cnt;
|
int zeros = MIN_EXPONENT_DIGITS - exponent_digit_cnt;
|
||||||
if (start + zeros + exponent_digit_cnt + 1
|
if (start + zeros + exponent_digit_cnt + 1
|
||||||
< buffer + buf_len) {
|
< buffer + buf_size) {
|
||||||
memmove(start + zeros, start,
|
memmove(start + zeros, start,
|
||||||
exponent_digit_cnt + 1);
|
exponent_digit_cnt + 1);
|
||||||
memset(start, '0', zeros);
|
memset(start, '0', zeros);
|
||||||
|
|
@ -368,6 +369,49 @@ PyOS_ascii_formatd(char *buffer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If format_char is 'Z', make sure we have at least one character
|
||||||
|
after the decimal point (and make sure we have a decimal point). */
|
||||||
|
if (format_char == 'Z') {
|
||||||
|
int insert_count = 0;
|
||||||
|
char* chars_to_insert;
|
||||||
|
|
||||||
|
/* search for the first non-digit character */
|
||||||
|
p = buffer;
|
||||||
|
while (*p && isdigit(Py_CHARMASK(*p)))
|
||||||
|
++p;
|
||||||
|
|
||||||
|
if (*p == '.') {
|
||||||
|
if (isdigit(Py_CHARMASK(*(p+1)))) {
|
||||||
|
/* Nothing to do, we already have a decimal
|
||||||
|
point and a digit after it */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* We have a decimal point, but no following
|
||||||
|
digit. Insert a zero after the decimal. */
|
||||||
|
++p;
|
||||||
|
chars_to_insert = "0";
|
||||||
|
insert_count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
chars_to_insert = ".0";
|
||||||
|
insert_count = 2;
|
||||||
|
}
|
||||||
|
if (insert_count) {
|
||||||
|
size_t buf_len = strlen(buffer);
|
||||||
|
if (buf_len + insert_count + 1 >= buf_size) {
|
||||||
|
/* If there is not enough room in the buffer
|
||||||
|
for the additional text, just skip it. It's
|
||||||
|
not worth generating an error over. */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memmove(p + insert_count, p,
|
||||||
|
buffer + strlen(buffer) - p + 1);
|
||||||
|
memcpy(p, chars_to_insert, insert_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue