]> granicus.if.org Git - python/commitdiff
Issue #5920: Changed format.__float__ and complex.__float__ to use a precision of...
authorEric Smith <eric@trueblade.com>
Tue, 5 May 2009 18:26:08 +0000 (18:26 +0000)
committerEric Smith <eric@trueblade.com>
Tue, 5 May 2009 18:26:08 +0000 (18:26 +0000)
Doc/c-api/conversion.rst
Include/floatobject.h
Lib/test/test_complex.py
Lib/test/test_float.py
Misc/NEWS
Objects/complexobject.c
Objects/floatobject.c
Objects/stringlib/formatter.h
Python/pystrtod.c

index 4d1f42f0688ee57784e59be605aadb67fbbd0eeb..fc42508ede4a424a70e12edee756e528c9982174 100644 (file)
@@ -86,10 +86,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:
index 60ede40f859261e2507bc69751274250a4ab9067..6c11036190aa44fbcd0dbdc340283d4d0d49a828 100644 (file)
@@ -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
index 557e952bd195147c7714de70e47302181599906d..56e9083eb74d8362690defd6442bb35906a49470 100644 (file)
@@ -467,6 +467,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')
index 971c14f441d66773986fc11aa6ca6fcba74b06e5..1c6c4124c7debf9f7e47ff183160034c02ff57c7 100644 (file)
@@ -257,6 +257,53 @@ class IEEEFormatTestCase(unittest.TestCase):
             self.assertEquals(math.atan2(float('-1e-1000'), -1),
                               math.atan2(-0.0, -1))
 
+    def test_format(self):
+        # these should be rewritten to use both format(x, spec) and
+        # x.__format__(spec)
+
+        self.assertEqual(format(0.0, 'f'), '0.000000')
+
+        # the default is 'g', except for empty format spec
+        self.assertEqual(format(0.0, ''), '0.0')
+        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')
+
+        self.assertEqual(format(-1.0, 'f'), '-1.000000')
+
+        self.assertEqual(format( 1.0, ' f'), ' 1.000000')
+        self.assertEqual(format(-1.0, ' f'), '-1.000000')
+        self.assertEqual(format( 1.0, '+f'), '+1.000000')
+        self.assertEqual(format(-1.0, '+f'), '-1.000000')
+
+        # % formatting
+        self.assertEqual(format(-1.0, '%'), '-100.000000%')
+
+        # conversion to string should fail
+        self.assertRaises(ValueError, format, 3.0, "s")
+
+        # other format specifiers shouldn't work on floats,
+        #  in particular int specifiers
+        for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] +
+                            [chr(x) for x in range(ord('A'), ord('Z')+1)]):
+            if not format_spec in 'eEfFgGn%':
+                self.assertRaises(ValueError, format, 0.0, format_spec)
+                self.assertRaises(ValueError, format, 1.0, format_spec)
+                self.assertRaises(ValueError, format, -1.0, format_spec)
+                self.assertRaises(ValueError, format, 1e100, format_spec)
+                self.assertRaises(ValueError, format, -1e100, format_spec)
+                self.assertRaises(ValueError, format, 1e-100, format_spec)
+                self.assertRaises(ValueError, format, -1e-100, format_spec)
+
     @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
                          "test requires IEEE 754 doubles")
     def test_format_testfile(self):
index 26af9bb49d828e7c42cb965df8cc13af98d859f5..d6c6227a876c89efcce001a791b33813b018913b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,15 @@ What's New in Python 2.7 alpha 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.
+
 - Issue #5890: in subclasses of 'property' the __doc__ attribute was
   shadowed by classtype's, even if it was None.  property now
   inserts the __doc__ into the subclass instance __dict__.
index f0f25415a78d36f1bcfba0e76393a97a90aecc4b..8bba241f5c20734fcc83d8ccee34696fce6c284c 100644 (file)
@@ -354,7 +354,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;
@@ -374,7 +374,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;
@@ -382,7 +382,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;
@@ -390,7 +390,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;
@@ -421,7 +421,10 @@ complex_print(PyComplexObject *v, FILE *fp, int flags)
 {
        PyObject *formatv;
        char *buf;
-       formatv = complex_format(v, (flags & Py_PRINT_RAW) ? 's' : 'r');
+        if (flags & Py_PRINT_RAW)
+            formatv = complex_format(v, PyFloat_STR_PRECISION, 'g');
+        else
+            formatv = complex_format(v, 0, 'r');
        if (formatv == NULL)
                return -1;
        buf = PyString_AS_STRING(formatv);
@@ -435,13 +438,13 @@ complex_print(PyComplexObject *v, FILE *fp, int flags)
 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
index 9a0dbb33c210cc6e5f0602b34da5d98719ab94a1..6b9a310dd337e388511f94fe246161cf0e918f14 100644 (file)
@@ -352,7 +352,7 @@ convert_to_double(PyObject **v, double *dbl)
 void
 PyFloat_AsString(char *buf, PyFloatObject *v)
 {
-       _PyOS_double_to_string(buf, 100, v->ob_fval, 's', 0,
+       _PyOS_double_to_string(buf, 100, v->ob_fval, 'g', PyFloat_STR_PRECISION,
                               Py_DTSF_ADD_DOT_0, NULL);
 }
 
@@ -368,9 +368,13 @@ static int
 float_print(PyFloatObject *v, FILE *fp, int flags)
 {
        char buf[100];
-       _PyOS_double_to_string(buf, sizeof(buf), v->ob_fval,
-                              (flags & Py_PRINT_RAW) ? 's' : 'r',
-                              0, Py_DTSF_ADD_DOT_0, NULL);
+        if (flags & Py_PRINT_RAW)
+            _PyOS_double_to_string(buf, sizeof(buf), v->ob_fval,
+                                   'g', PyFloat_STR_PRECISION,
+                                   Py_DTSF_ADD_DOT_0, NULL);
+        else
+            _PyOS_double_to_string(buf, sizeof(buf), v->ob_fval,
+                                   'r', 0, Py_DTSF_ADD_DOT_0, NULL);
        Py_BEGIN_ALLOW_THREADS
        fputs(buf, fp);
        Py_END_ALLOW_THREADS
@@ -390,7 +394,8 @@ static PyObject *
 float_str(PyFloatObject *v)
 {
        char buf[100];
-       _PyOS_double_to_string(buf, sizeof(buf), v->ob_fval, 's', 0,
+       _PyOS_double_to_string(buf, sizeof(buf), v->ob_fval, 'g',
+                               PyFloat_STR_PRECISION,
                               Py_DTSF_ADD_DOT_0, NULL);
        return PyString_FromString(buf);
 }
index 1f3c535b4ae759e87dcb0641fd17fbae62a27d30..3b2218128dd98e6589db0ffc15a5b7cd30f9c8d3 100644 (file)
@@ -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"
index 2be383442d96ef9fe4cbf04cafb87dc7d879419f..79f63e2603538d2f72964613eb28a4d15cf7fcf0 100644 (file)
@@ -660,16 +660,15 @@ _PyOS_double_to_string(char *buf, size_t buf_len, double val,
                /* Supplied precision is unused, must be 0. */
                if (precision != 0)
                        return;
+               /* 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)
-                       return;
-               precision = 12;
-               format_code = 'g';
-               break;
        default:
                assert(0);
                return;