From 388122d43b2b6bb41774d9680b9ad3bc05682f85 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Wed, 4 Aug 2010 20:56:28 +0000 Subject: Issue #9337: Make float.__str__ identical to float.__repr__. (And similarly for complex numbers.) --- Doc/tutorial/floatingpoint.rst | 11 +++---- Include/floatobject.h | 6 ---- Lib/test/formatfloat_testcases.txt | 64 +++++++++++++++++--------------------- Lib/test/test_float.py | 4 ++- Lib/test/test_tokenize.py | 4 +-- Lib/test/test_unicodedata.py | 9 +++--- Misc/NEWS | 3 ++ Objects/complexobject.c | 8 +---- Objects/floatobject.c | 22 +++---------- Objects/stringlib/formatter.h | 17 ++++++---- 10 files changed, 63 insertions(+), 85 deletions(-) diff --git a/Doc/tutorial/floatingpoint.rst b/Doc/tutorial/floatingpoint.rst index c06568e..863fb28 100644 --- a/Doc/tutorial/floatingpoint.rst +++ b/Doc/tutorial/floatingpoint.rst @@ -92,18 +92,17 @@ thing in all languages that support your hardware's floating-point arithmetic (although some languages may not *display* the difference by default, or in all output modes). -Python's built-in :func:`str` function produces only 12 significant digits, and -you may wish to use that instead. It's unusual for ``eval(str(x))`` to -reproduce *x*, but the output may be more pleasant to look at:: +For more pleasant output, you may may wish to use string formatting to produce a limited number of significant digits:: - >>> str(math.pi) + >>> format(math.pi, '.12g') # give 12 significant digits '3.14159265359' + >>> format(math.pi, '.2f') # give 2 digits after the point + '3.14' + >>> repr(math.pi) '3.141592653589793' - >>> format(math.pi, '.2f') - '3.14' It's important to realize that this is, in a real sense, an illusion: you're simply rounding the *display* of the true machine value. diff --git a/Include/floatobject.h b/Include/floatobject.h index 5b8d1a1..364b913 100644 --- a/Include/floatobject.h +++ b/Include/floatobject.h @@ -21,12 +21,6 @@ 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/formatfloat_testcases.txt b/Lib/test/formatfloat_testcases.txt index 4cf20aa..9f045b7 100644 --- a/Lib/test/formatfloat_testcases.txt +++ b/Lib/test/formatfloat_testcases.txt @@ -314,43 +314,37 @@ %#.5g 234.56 -> 234.56 %#.6g 234.56 -> 234.560 --- for repr formatting see the separate test_short_repr test in --- test_float.py. Not all platforms use short repr for floats. - --- str formatting. Result always includes decimal point and at +-- repr formatting. Result always includes decimal point and at -- least one digit after the point, or an exponent. -%s 0 -> 0.0 -%s 1 -> 1.0 - -%s 0.01 -> 0.01 -%s 0.02 -> 0.02 -%s 0.03 -> 0.03 -%s 0.04 -> 0.04 -%s 0.05 -> 0.05 +%r 0 -> 0.0 +%r 1 -> 1.0 --- str truncates to 12 significant digits -%s 1.234123412341 -> 1.23412341234 -%s 1.23412341234 -> 1.23412341234 -%s 1.2341234123 -> 1.2341234123 +%r 0.01 -> 0.01 +%r 0.02 -> 0.02 +%r 0.03 -> 0.03 +%r 0.04 -> 0.04 +%r 0.05 -> 0.05 --- values >= 1e11 get an exponent -%s 10 -> 10.0 -%s 100 -> 100.0 -%s 1e10 -> 10000000000.0 -%s 9.999e10 -> 99990000000.0 -%s 99999999999 -> 99999999999.0 -%s 99999999999.9 -> 99999999999.9 -%s 99999999999.99 -> 1e+11 -%s 1e11 -> 1e+11 -%s 1e12 -> 1e+12 +-- values >= 1e16 get an exponent +%r 10 -> 10.0 +%r 100 -> 100.0 +%r 1e15 -> 1000000000000000.0 +%r 9.999e15 -> 9999000000000000.0 +%r 9999999999999998 -> 9999999999999998.0 +%r 9999999999999999 -> 1e+16 +%r 1e16 -> 1e+16 +%r 1e17 -> 1e+17 -- as do values < 1e-4 -%s 1e-3 -> 0.001 -%s 1.001e-4 -> 0.0001001 -%s 1.000000000001e-4 -> 0.0001 -%s 1.00000000001e-4 -> 0.000100000000001 -%s 1.0000000001e-4 -> 0.00010000000001 -%s 1e-4 -> 0.0001 -%s 0.999999999999e-4 -> 9.99999999999e-05 -%s 0.999e-4 -> 9.99e-05 -%s 1e-5 -> 1e-05 +%r 1e-3 -> 0.001 +%r 1.001e-4 -> 0.0001001 +%r 1.0000000000000001e-4 -> 0.0001 +%r 1.000000000000001e-4 -> 0.0001000000000000001 +%r 1.00000000001e-4 -> 0.000100000000001 +%r 1.0000000001e-4 -> 0.00010000000001 +%r 1e-4 -> 0.0001 +%r 0.99999999999999999e-4 -> 0.0001 +%r 0.9999999999999999e-4 -> 9.999999999999999e-05 +%r 0.999999999999e-4 -> 9.99999999999e-05 +%r 0.999e-4 -> 9.99e-05 +%r 1e-5 -> 1e-05 diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index a1b130b..ac5fc33 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -617,7 +617,9 @@ class ReprTestCase(unittest.TestCase): negs = '-'+s self.assertEqual(s, repr(float(s))) self.assertEqual(negs, repr(float(negs))) - + # Since Python 3.2, repr and str are identical + self.assertEqual(repr(float(s)), str(float(s))) + self.assertEqual(repr(float(negs)), str(float(negs))) @requires_IEEE_754 class RoundTestCase(unittest.TestCase): diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index d0853d3..4b56699 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -598,11 +598,11 @@ def decistmt(s): The format of the exponent is inherited from the platform C library. Known cases are "e-007" (Windows) and "e-07" (not Windows). Since - we're only showing 12 digits, and the 13th isn't close to 5, the + we're only showing 11 digits, and the 12th isn't close to 5, the rest of the output should be platform-independent. >>> exec(s) #doctest: +ELLIPSIS - -3.21716034272e-0...7 + -3.2171603427...e-0...7 Output from calculations with Decimal should be identical across all platforms. diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index 59e6d39..b572261 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -80,8 +80,7 @@ class UnicodeDatabaseTest(unittest.TestCase): class UnicodeFunctionsTest(UnicodeDatabaseTest): # update this, if the database changes - expectedchecksum = '6ccf1b1a36460d2694f9b0b0f0324942fe70ede6' - + expectedchecksum = 'e89a6380093a00a7685ac7b92e7367d737fcb79b' def test_function_checksum(self): data = [] h = hashlib.sha1() @@ -90,9 +89,9 @@ class UnicodeFunctionsTest(UnicodeDatabaseTest): char = chr(i) data = [ # Properties - str(self.db.digit(char, -1)), - str(self.db.numeric(char, -1)), - str(self.db.decimal(char, -1)), + format(self.db.digit(char, -1), '.12g'), + format(self.db.numeric(char, -1), '.12g'), + format(self.db.decimal(char, -1), '.12g'), self.db.category(char), self.db.bidirectional(char), self.db.decomposition(char), diff --git a/Misc/NEWS b/Misc/NEWS index 6bc3e5c..4594a88 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 3.2 Alpha 2? Core and Builtins ----------------- +- Issue #9337: The str() of a float or complex number is now identical + to its repr(). + - Issue #9416: Fix some issues with complex formatting where the output with no type specifier failed to match the str output: diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 7594c88..674362f 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -394,12 +394,6 @@ complex_repr(PyComplexObject *v) return complex_format(v, 0, 'r'); } -static PyObject * -complex_str(PyComplexObject *v) -{ - return complex_format(v, PyFloat_STR_PRECISION, 'g'); -} - static long complex_hash(PyComplexObject *v) { @@ -1104,7 +1098,7 @@ PyTypeObject PyComplex_Type = { 0, /* tp_as_mapping */ (hashfunc)complex_hash, /* tp_hash */ 0, /* tp_call */ - (reprfunc)complex_str, /* tp_str */ + (reprfunc)complex_repr, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index c757203..b792c19 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -305,32 +305,20 @@ convert_to_double(PyObject **v, double *dbl) } static PyObject * -float_str_or_repr(PyFloatObject *v, int precision, char format_code) +float_repr(PyFloatObject *v) { PyObject *result; char *buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v), - format_code, precision, + 'r', 0, Py_DTSF_ADD_DOT_0, NULL); if (!buf) - return PyErr_NoMemory(); + return PyErr_NoMemory(); result = PyUnicode_FromString(buf); PyMem_Free(buf); return result; } -static PyObject * -float_repr(PyFloatObject *v) -{ - return float_str_or_repr(v, 0, 'r'); -} - -static PyObject * -float_str(PyFloatObject *v) -{ - return float_str_or_repr(v, PyFloat_STR_PRECISION, 'g'); -} - /* Comparison is pretty much a nightmare. When comparing float to float, * we do it as straightforwardly (and long-windedly) as conceivable, so * that, e.g., Python x == y delivers the same result as the platform @@ -1169,7 +1157,7 @@ float_hex(PyObject *v) CONVERT_TO_DOUBLE(v, x); if (Py_IS_NAN(x) || Py_IS_INFINITY(x)) - return float_str((PyFloatObject *)v); + return float_repr((PyFloatObject *)v); if (x == 0.0) { if (copysign(1.0, x) == -1.0) @@ -1873,7 +1861,7 @@ PyTypeObject PyFloat_Type = { 0, /* tp_as_mapping */ (hashfunc)float_hash, /* tp_hash */ 0, /* tp_call */ - (reprfunc)float_str, /* tp_str */ + (reprfunc)float_repr, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ diff --git a/Objects/stringlib/formatter.h b/Objects/stringlib/formatter.h index ab57a82..4fdab06 100644 --- a/Objects/stringlib/formatter.h +++ b/Objects/stringlib/formatter.h @@ -950,11 +950,12 @@ 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, and different default precision.*/ - type = 'g'; - default_precision = PyFloat_STR_PRECISION; + /* Omitted type specifier. Behaves in the same way as repr(x) + and str(x) if no precision is given, else like 'g', but with + at least one digit after the decimal point. */ flags |= Py_DTSF_ADD_DOT_0; + type = 'r'; + default_precision = 0; } if (type == 'n') @@ -974,6 +975,8 @@ format_float_internal(PyObject *value, if (precision < 0) precision = default_precision; + else if (type == 'r') + type = 'g'; /* 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" @@ -1134,8 +1137,8 @@ format_complex_internal(PyObject *value, if (type == '\0') { /* Omitted type specifier. Should be like str(self). */ - type = 'g'; - default_precision = PyFloat_STR_PRECISION; + type = 'r'; + default_precision = 0; if (re == 0.0 && copysign(1.0, re) == 1.0) skip_re = 1; else @@ -1149,6 +1152,8 @@ format_complex_internal(PyObject *value, if (precision < 0) precision = default_precision; + else if (type == 'r') + type = 'g'; /* 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" -- cgit v0.12