From 63376228a3f2d3ac4a21a10c0653c3b984c2d686 Mon Sep 17 00:00:00 2001 From: Eric Smith Date: Tue, 5 May 2009 14:04:18 +0000 Subject: Issue #5920: Changed format.__float__ and complex.__float__ to use a precision of 12 when using the empty presentation type. This more closely matches str()'s behavior and reduces surprises when adding alignment flags to an empty format string. Patch by Mark Dickinson. --- Doc/c-api/conversion.rst | 8 ++++---- Include/floatobject.h | 6 ++++++ Lib/test/test_complex.py | 10 ++++++++++ Lib/test/test_float.py | 7 +++++++ Misc/NEWS | 9 +++++++++ Objects/complexobject.c | 12 +++++------ Objects/floatobject.c | 9 +++++---- Objects/stringlib/formatter.h | 12 +++++++---- Python/pystrtod.c | 46 ++++++++++++------------------------------- 9 files changed, 68 insertions(+), 51 deletions(-) diff --git a/Doc/c-api/conversion.rst b/Doc/c-api/conversion.rst index 403c183..318842c 100644 --- a/Doc/c-api/conversion.rst +++ b/Doc/c-api/conversion.rst @@ -119,10 +119,10 @@ The following functions provide locale-independent string to number conversions. Convert a :ctype:`double` *val* to a string using supplied *format_code*, *precision*, and *flags*. - *format_code* must be one of ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``, - ``'G'``, ``'s'``, or ``'r'``. For ``'s'`` and ``'r'``, the supplied - *precision* must be 0 and is ignored. These specify the standard - :func:`str` and :func:`repr` formats, respectively. + *format_code* must be one of ``'e'``, ``'E'``, ``'f'``, ``'F'``, + ``'g'``, ``'G'`` or ``'r'``. For ``'r'``, the supplied *precision* + must be 0 and is ignored. The ``'r'`` format code specifies the + standard :func:`repr` format. *flags* can be zero or more of the values *Py_DTSF_SIGN*, *Py_DTSF_ADD_DOT_0*, or *Py_DTSF_ALT*, or-ed together: diff --git a/Include/floatobject.h b/Include/floatobject.h index 364b913..5b8d1a1 100644 --- a/Include/floatobject.h +++ b/Include/floatobject.h @@ -21,6 +21,12 @@ PyAPI_DATA(PyTypeObject) PyFloat_Type; #define PyFloat_Check(op) PyObject_TypeCheck(op, &PyFloat_Type) #define PyFloat_CheckExact(op) (Py_TYPE(op) == &PyFloat_Type) +/* The str() precision PyFloat_STR_PRECISION is chosen so that in most cases, + the rounding noise created by various operations is suppressed, while + giving plenty of precision for practical use. */ + +#define PyFloat_STR_PRECISION 12 + #ifdef Py_NAN #define Py_RETURN_NAN return PyFloat_FromDouble(Py_NAN) #endif diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index ac19353..1593f7b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -445,6 +445,16 @@ class ComplexTest(unittest.TestCase): self.assertEqual(format(3+0j, ''), str(3+0j)) self.assertEqual(format(3.2+0j, ''), str(3.2+0j)) + # empty presentation type should still be analogous to str, + # even when format string is nonempty (issue #5920). + self.assertEqual(format(3.2+0j, '-'), str(3.2+0j)) + self.assertEqual(format(3.2+0j, '<'), str(3.2+0j)) + z = 4/7. - 100j/7. + self.assertEqual(format(z, ''), str(z)) + self.assertEqual(format(z, '-'), str(z)) + self.assertEqual(format(z, '<'), str(z)) + self.assertEqual(format(z, '10'), str(z)) + self.assertEqual(format(1+3j, 'g'), '1+3j') self.assertEqual(format(3j, 'g'), '0+3j') self.assertEqual(format(1.5+3.5j, 'g'), '1.5+3.5j') diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index fb6daaf..b617fa3 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -284,6 +284,13 @@ class FormatTestCase(unittest.TestCase): self.assertEqual(format(0.01, ''), '0.01') self.assertEqual(format(0.01, 'g'), '0.01') + # empty presentation type should format in the same way as str + # (issue 5920) + x = 100/7. + self.assertEqual(format(x, ''), str(x)) + self.assertEqual(format(x, '-'), str(x)) + self.assertEqual(format(x, '>'), str(x)) + self.assertEqual(format(x, '2'), str(x)) self.assertEqual(format(1.0, 'f'), '1.000000') diff --git a/Misc/NEWS b/Misc/NEWS index d388be1..dbd10b2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,15 @@ What's New in Python 3.1 beta 1? Core and Builtins ----------------- +- Issue #5920: For float.__format__, change the behavior with the + empty presentation type (that is, not one of 'e', 'f', 'g', or 'n') + to be like 'g' but with at least one decimal point and with a + default precision of 12. Previously, the behavior the same but with + a default precision of 6. This more closely matches str(), and + reduces surprises when adding alignment flags to the empty + presentation type. This also affects the new complex.__format__ in + the same way. + - Implement PEP 383, Non-decodable Bytes in System Character Interfaces. - Issue #5890: in subclasses of 'property' the __doc__ attribute was diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 8fdbd37..0d13edf 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -330,7 +330,7 @@ complex_dealloc(PyObject *op) static PyObject * -complex_format(PyComplexObject *v, char format_code) +complex_format(PyComplexObject *v, int precision, char format_code) { PyObject *result = NULL; Py_ssize_t len; @@ -350,7 +350,7 @@ complex_format(PyComplexObject *v, char format_code) if (v->cval.real == 0. && copysign(1.0, v->cval.real)==1.0) { re = ""; im = PyOS_double_to_string(v->cval.imag, format_code, - 0, 0, NULL); + precision, 0, NULL); if (!im) { PyErr_NoMemory(); goto done; @@ -358,7 +358,7 @@ complex_format(PyComplexObject *v, char format_code) } else { /* Format imaginary part with sign, real part without */ pre = PyOS_double_to_string(v->cval.real, format_code, - 0, 0, NULL); + precision, 0, NULL); if (!pre) { PyErr_NoMemory(); goto done; @@ -366,7 +366,7 @@ complex_format(PyComplexObject *v, char format_code) re = pre; im = PyOS_double_to_string(v->cval.imag, format_code, - 0, Py_DTSF_SIGN, NULL); + precision, Py_DTSF_SIGN, NULL); if (!im) { PyErr_NoMemory(); goto done; @@ -395,13 +395,13 @@ complex_format(PyComplexObject *v, char format_code) static PyObject * complex_repr(PyComplexObject *v) { - return complex_format(v, 'r'); + return complex_format(v, 0, 'r'); } static PyObject * complex_str(PyComplexObject *v) { - return complex_format(v, 's'); + return complex_format(v, PyFloat_STR_PRECISION, 'g'); } static long diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 1071fae..1074f3d 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -294,11 +294,12 @@ convert_to_double(PyObject **v, double *dbl) } static PyObject * -float_str_or_repr(PyFloatObject *v, char format_code) +float_str_or_repr(PyFloatObject *v, int precision, char format_code) { PyObject *result; char *buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v), - format_code, 0, Py_DTSF_ADD_DOT_0, + format_code, precision, + Py_DTSF_ADD_DOT_0, NULL); if (!buf) return PyErr_NoMemory(); @@ -310,13 +311,13 @@ float_str_or_repr(PyFloatObject *v, char format_code) static PyObject * float_repr(PyFloatObject *v) { - return float_str_or_repr(v, 'r'); + return float_str_or_repr(v, 0, 'r'); } static PyObject * float_str(PyFloatObject *v) { - return float_str_or_repr(v, 's'); + return float_str_or_repr(v, PyFloat_STR_PRECISION, 'g'); } /* Comparison is pretty much a nightmare. When comparing float to float, diff --git a/Objects/stringlib/formatter.h b/Objects/stringlib/formatter.h index 1f3c535..3b22181 100644 --- a/Objects/stringlib/formatter.h +++ b/Objects/stringlib/formatter.h @@ -881,6 +881,7 @@ format_float_internal(PyObject *value, int has_decimal; double val; Py_ssize_t precision = format->precision; + Py_ssize_t default_precision = 6; STRINGLIB_CHAR type = format->type; int add_pct = 0; STRINGLIB_CHAR *p; @@ -907,9 +908,10 @@ format_float_internal(PyObject *value, } if (type == '\0') { - /* Omitted type specifier. This is like 'g' but with at least - one digit after the decimal point. */ + /* Omitted type specifier. This is like 'g' but with at least one + digit after the decimal point, and different default precision.*/ type = 'g'; + default_precision = PyFloat_STR_PRECISION; flags |= Py_DTSF_ADD_DOT_0; } @@ -933,7 +935,7 @@ format_float_internal(PyObject *value, } if (precision < 0) - precision = 6; + precision = default_precision; #if PY_VERSION_HEX < 0x03010000 /* 3.1 no longer converts large 'f' to 'g'. */ @@ -1039,6 +1041,7 @@ format_complex_internal(PyObject *value, int re_has_decimal; int im_has_decimal; Py_ssize_t precision = format->precision; + Py_ssize_t default_precision = 6; STRINGLIB_CHAR type = format->type; STRINGLIB_CHAR *p_re; STRINGLIB_CHAR *p_im; @@ -1100,6 +1103,7 @@ format_complex_internal(PyObject *value, if (type == '\0') { /* Omitted type specifier. Should be like str(self). */ type = 'g'; + default_precision = PyFloat_STR_PRECISION; add_parens = 1; if (re == 0.0) skip_re = 1; @@ -1115,7 +1119,7 @@ format_complex_internal(PyObject *value, type = 'f'; if (precision < 0) - precision = 6; + precision = default_precision; /* Cast "type", because if we're in unicode we need to pass a 8-bit char. This is safe, because we've restricted what "type" diff --git a/Python/pystrtod.c b/Python/pystrtod.c index a50d360..d36f931 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -746,18 +746,15 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val, PyErr_BadInternalCall(); return NULL; } + /* The repr() precision (17 significant decimal digits) is the + minimal number that is guaranteed to have enough precision + so that if the number is read back in the exact same binary + value is recreated. This is true for IEEE floating point + by design, and also happens to work for all other modern + hardware. */ precision = 17; format_code = 'g'; break; - case 's': /* str format */ - /* Supplied precision is unused, must be 0. */ - if (precision != 0) { - PyErr_BadInternalCall(); - return NULL; - } - precision = 12; - format_code = 'g'; - break; default: PyErr_BadInternalCall(); return NULL; @@ -889,18 +886,19 @@ static char *uc_float_strings[] = { Arguments: d is the double to be converted - format_code is one of 'e', 'f', 'g', 'r' or 's'. 'e', 'f' and 'g' - correspond to '%e', '%f' and '%g'; 'r' and 's' correspond - to repr and str. + format_code is one of 'e', 'f', 'g', 'r'. 'e', 'f' and 'g' + correspond to '%e', '%f' and '%g'; 'r' corresponds to repr. mode is one of '0', '2' or '3', and is completely determined by - format_code: 'e', 'g' and 's' use mode 2; 'f' mode 3, 'r' mode 0. + format_code: 'e' and 'g' use mode 2; 'f' mode 3, 'r' mode 0. precision is the desired precision always_add_sign is nonzero if a '+' sign should be included for positive numbers add_dot_0_if_integer is nonzero if integers in non-exponential form - should have ".0" added. Only applies to format codes 'r', 's', and 'g'. + should have ".0" added. Only applies to format codes 'r' and 'g'. use_alt_formatting is nonzero if alternative formatting should be - used. Only applies to format codes 'e', 'f' and 'g'. + used. Only applies to format codes 'e', 'f' and 'g'. For code 'g', + at most one of use_alt_formatting and add_dot_0_if_integer should + be nonzero. type, if non-NULL, will be set to one of these constants to identify the type of the 'd' argument: Py_DTST_FINITE @@ -1041,13 +1039,6 @@ format_float_short(double d, char format_code, if (decpt <= -4 || decpt > 16) use_exp = 1; break; - case 's': - /* if we're forcing a digit after the point, convert to - exponential format at 1e11. If not, convert at 1e12. */ - if (decpt <= -4 || decpt > - (add_dot_0_if_integer ? precision-1 : precision)) - use_exp = 1; - break; default: PyErr_BadInternalCall(); goto exit; @@ -1220,17 +1211,6 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val, } break; - /* str format */ - case 's': - mode = 2; - /* Supplied precision is unused, must be 0. */ - if (precision != 0) { - PyErr_BadInternalCall(); - return NULL; - } - precision = 12; - break; - default: PyErr_BadInternalCall(); return NULL; -- cgit v0.12