]> granicus.if.org Git - python/commitdiff
Issue #5835, deprecate PyOS_ascii_formatd.
authorEric Smith <eric@trueblade.com>
Sat, 25 Apr 2009 21:40:15 +0000 (21:40 +0000)
committerEric Smith <eric@trueblade.com>
Sat, 25 Apr 2009 21:40:15 +0000 (21:40 +0000)
If anyone wants to clean up the documentation, feel free. It's my first documentation foray, and it's not that great.

Will port to py3k with a different strategy.

Doc/c-api/conversion.rst
Include/pystrtod.h
Lib/test/test_ascii_formatd.py [new file with mode: 0644]
Misc/NEWS
Modules/cPickle.c
Objects/floatobject.c
Objects/stringobject.c
Objects/unicodeobject.c
Python/pystrtod.c

index 0c81bc087c45892b78c6558d5a665fea174dc165..e391b7656a4324809105c102bc317d667b737631 100644 (file)
@@ -76,7 +76,42 @@ The following functions provide locale-independent string to number conversions.
    the conversion failed.
 
    .. versionadded:: 2.4
+   .. deprecated:: 2.7
+      This function is removed in Python 2.7 and 3.1.  Use :func:`PyOS_double_to_string`
+      instead.
 
+.. cfunction:: char * PyOS_double_to_string(double val, char format_code, int precision, int flags, int *ptype)
+
+   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
+   standards :func:`str` and :func:`repr` formats, respectively.
+
+   *flags* can be zero or more of the values *Py_DTSF_SIGN*,
+   *Py_DTSF_ADD_DOT_0*, or *Py_DTSF_ALT*, and-ed together.
+
+       *Py_DTSF_SIGN* means always precede the returned string with a
+       sign character, even if *val* is non-negative.
+
+       *Py_DTSF_ADD_DOT_0* means ensure that the returned string will
+       not look like an integer.
+
+       *Py_DTSF_ALT* means apply "alternate" formatting rules. See the
+       documentation for the :func:`PyOS_snprintf` ``'#'`` specifier
+       for details.
+
+   If *ptype* is non-NULL, then the value it points to will be set to
+   one of *Py_DTST_FINITE*, *Py_DTST_INFINITE*, or *Py_DTST_NAN*,
+   signifying that *val* is a finite number, an infinite number, or
+   not a number, respectively.
+
+   The return value is a pointer to *buffer* with the converted string or NULL if
+   the conversion failed.
+
+   .. versionadded:: 2.7
 
 .. cfunction:: double PyOS_ascii_atof(const char *nptr)
 
index 1caa7aedfb73a8178d889fe6d0ab121611750433..106448bc375ac2c9eadc5dd034dd13ef77faf2cf 100644 (file)
@@ -8,7 +8,17 @@ extern "C" {
 
 PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr);
 PyAPI_FUNC(double) PyOS_ascii_atof(const char *str);
-PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len,  const char *format, double d);
+
+/* Deprecated in 2.7 and 3.1. Will disappear in 2.8 (if it exists) and 3.2 */
+PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len,
+                                      const char *format, double d);
+
+/* Use PyOS_double_to_string instead. It's the same, except it allocates
+   the appropriately sized buffer and returns it. This function will go
+   away in Python 2.8 and 3.2. */
+PyAPI_FUNC(void) _PyOS_double_to_string(char *buf, size_t buf_len, double val,
+                                        char format_code, int precision,
+                                        int flags, int* type);
 
 /* The caller is responsible for calling PyMem_Free to free the buffer
    that's is returned. */
diff --git a/Lib/test/test_ascii_formatd.py b/Lib/test/test_ascii_formatd.py
new file mode 100644 (file)
index 0000000..3501955
--- /dev/null
@@ -0,0 +1,62 @@
+# PyOS_ascii_formatd is deprecated and not called from anywhere in
+#  Python itself. So this module is the only place it gets tested.
+# Test that it works, and test that it's deprecated.
+
+import unittest
+from test_support import check_warnings, run_unittest, cpython_only
+
+
+class FormatDeprecationTests(unittest.TestCase):
+
+    @cpython_only
+    def testFormatDeprecation(self):
+        # delay importing ctypes until we know we're in CPython
+        from ctypes import (pythonapi, create_string_buffer, sizeof, byref,
+                            c_double)
+        PyOS_ascii_formatd = pythonapi.PyOS_ascii_formatd
+        buf = create_string_buffer(' ' * 100)
+
+        with check_warnings() as w:
+            PyOS_ascii_formatd(byref(buf), sizeof(buf), '%+.10f',
+                               c_double(10.0))
+            self.assertEqual(buf.value, '+10.0000000000')
+
+        self.assertEqual(str(w.message), 'PyOS_ascii_formatd is deprecated, '
+                         'use PyOS_double_to_string instead')
+
+class FormatTests(unittest.TestCase):
+    # ensure that, for the restricted set of format codes,
+    # %-formatting returns the same values os PyOS_ascii_formatd
+    @cpython_only
+    def testFormat(self):
+        # delay importing ctypes until we know we're in CPython
+        from ctypes import (pythonapi, create_string_buffer, sizeof, byref,
+                            c_double)
+        PyOS_ascii_formatd = pythonapi.PyOS_ascii_formatd
+        buf = create_string_buffer(' ' * 100)
+
+        tests = [
+            ('%f', 100.0),
+            ('%g', 100.0),
+            ('%#g', 100.0),
+            ('%#.2g', 100.0),
+            ('%#.2g', 123.4567),
+            ('%#.2g', 1.234567e200),
+            ('%e', 1.234567e200),
+            ('%e', 1.234),
+            ('%+e', 1.234),
+            ('%-e', 1.234),
+            ]
+
+        with check_warnings():
+            for format, val in tests:
+                PyOS_ascii_formatd(byref(buf), sizeof(buf), format,
+                                   c_double(val))
+                self.assertEqual(buf.value, format % val)
+
+
+def test_main():
+    run_unittest(FormatDeprecationTests, FormatTests)
+
+if __name__ == '__main__':
+    test_main()
index 7c36bdab4a35d501fd0e6a12857d7f3c8cd887c9..b21151236b1f43c8864c1b6b60021a6fe0eeea33 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
 Core and Builtins
 -----------------
 
+- Issue #5835: Deprecate PyOS_ascii_formatd and replace it with
+  _PyOS_double_to_string or PyOS_double_to_string.
+
 - Issue #5283: Setting __class__ in __del__ caused a segfault.
 
 - Issue #5816: complex(repr(z)) now recovers z exactly, even when
index fb13ba18bc459ecc56a9df125a9d42624015ae47..6c7ed9987e1f3f07752e84af715d2aa1cbb10b8c 100644 (file)
@@ -1166,7 +1166,8 @@ save_float(Picklerobject *self, PyObject *args)
        else {
                char c_str[250];
                c_str[0] = FLOAT;
-               PyOS_ascii_formatd(c_str + 1, sizeof(c_str) - 2, "%.17g", x);
+               _PyOS_double_to_string(c_str + 1, sizeof(c_str) - 2, x, 'g',
+                                       17, 0, NULL);
                /* Extend the formatted string with a newline character */
                strcat(c_str, "\n");
 
index 4f041f4820cce51d28dff0ec63ac85ceff515c7d..382b991bdfb419804fef4e102960df2eb65fcf41 100644 (file)
@@ -342,7 +342,6 @@ static void
 format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
 {
        register char *cp;
-       char format[32];
        int i;
 
        /* Subroutine for float_repr and float_print.
@@ -352,8 +351,8 @@ format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
           in such cases, we append ".0" to the string. */
 
        assert(PyFloat_Check(v));
-       PyOS_snprintf(format, 32, "%%.%ig", precision);
-       PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);
+       _PyOS_double_to_string(buf, buflen, v->ob_fval, 'g', precision,
+                               0, NULL);
        cp = buf;
        if (*cp == '-')
                cp++;
index 89614e6a3d6e26729b3e3f6270bb4a8744af4a93..316a271a95479c36ded021080f4d859b56fc416f 100644 (file)
@@ -4332,9 +4332,6 @@ Py_LOCAL_INLINE(int)
 formatfloat(char *buf, size_t buflen, int flags,
             int prec, int type, PyObject *v)
 {
-       /* fmt = '%#.' + `prec` + `type`
-          worst case length = 3 + 10 (len of INT_MAX) + 1 = 14 (use 20)*/
-       char fmt[20];
        double x;
        x = PyFloat_AsDouble(v);
        if (x == -1.0 && PyErr_Occurred()) {
@@ -4378,10 +4375,8 @@ formatfloat(char *buf, size_t buflen, int flags,
                        "formatted float is too long (precision too large?)");
                return -1;
        }
-       PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c",
-                     (flags&F_ALT) ? "#" : "",
-                     prec, type);
-       PyOS_ascii_formatd(buf, buflen, fmt, x);
+       _PyOS_double_to_string(buf, buflen, x, type, prec,
+                            (flags&F_ALT)?Py_DTSF_ALT:0, NULL);
        return (int)strlen(buf);
 }
 
index 6edc2f8347c2a7f6e8dc39d671b4902246fb5453..62191ade0ab815aefaf13c7d442b1f8bc76ccc7b 100644 (file)
@@ -8245,11 +8245,13 @@ strtounicode(Py_UNICODE *buffer, const char *charbuffer)
 }
 
 static int
-doubletounicode(Py_UNICODE *buffer, size_t len, const char *format, double x)
+doubletounicode(Py_UNICODE *buffer, size_t len, int format_code,
+                int precision, int flags, double x)
 {
     Py_ssize_t result;
 
-    PyOS_ascii_formatd((char *)buffer, len, format, x);
+    _PyOS_double_to_string((char *)buffer, len, x, format_code, precision,
+                           flags, NULL);
     result = strtounicode(buffer, (char *)buffer);
     return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
 }
@@ -8276,9 +8278,6 @@ formatfloat(Py_UNICODE *buf,
             int type,
             PyObject *v)
 {
-    /* fmt = '%#.' + `prec` + `type`
-       worst case length = 3 + 10 (len of INT_MAX) + 1 = 14 (use 20)*/
-    char fmt[20];
     double x;
 
     x = PyFloat_AsDouble(v);
@@ -8320,10 +8319,8 @@ formatfloat(Py_UNICODE *buf,
                         "formatted float is too long (precision too large?)");
         return -1;
     }
-    PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c",
-                  (flags&F_ALT) ? "#" : "",
-                  prec, type);
-    return doubletounicode(buf, buflen, fmt, x);
+    return doubletounicode(buf, buflen, type, prec,
+                           (flags&F_ALT)?Py_DTSF_ALT:0, x);
 }
 
 static PyObject*
index 703ae64563f0c50d2cfdfdaeb427d2cf62c7813c..f9a8831b11ef92785ad86f02aedf086eea99c730 100644 (file)
@@ -236,6 +236,25 @@ change_decimal_from_locale_to_dot(char* buffer)
 }
 
 
+Py_LOCAL_INLINE(void)
+ensure_sign(char* buffer, size_t buf_size)
+{
+       Py_ssize_t len;
+
+       if (buffer[0] == '-')
+               /* Already have a sign. */
+               return;
+
+       /* Include the trailing 0 byte. */
+       len = strlen(buffer)+1;
+       if (len >= buf_size+1)
+               /* No room for the sign, don't do anything. */
+               return;
+
+       memmove(buffer+1, buffer, len);
+       buffer[0] = '+';
+}
+
 /* From the C99 standard, section 7.19.6:
 The exponent always contains at least two digits, and only as many more digits
 as necessary to represent the exponent.
@@ -363,7 +382,7 @@ ensure_decimal_point(char* buffer, size_t buf_size)
 #define FLOAT_FORMATBUFLEN 120
 
 /**
- * PyOS_ascii_formatd:
+ * _PyOS_ascii_formatd:
  * @buffer: A buffer to place the resulting string in
  * @buf_size: The length of the buffer.
  * @format: The printf()-style format to use for the
@@ -380,7 +399,8 @@ ensure_decimal_point(char* buffer, size_t buf_size)
  *
  * Return value: The pointer to the buffer with the converted string.
  **/
-char *
+/* DEPRECATED, will be deleted in 2.8 and 3.2 */
+PyAPI_FUNC(char *)
 PyOS_ascii_formatd(char       *buffer, 
                   size_t      buf_size, 
                   const char *format, 
@@ -393,6 +413,11 @@ PyOS_ascii_formatd(char       *buffer,
           also with at least one character past the decimal. */
        char tmp_format[FLOAT_FORMATBUFLEN];
 
+       if (PyErr_WarnEx(PyExc_DeprecationWarning,
+                        "PyOS_ascii_formatd is deprecated, "
+                        "use PyOS_double_to_string instead", 1) < 0)
+               return NULL;
+
        /* The last character in the format string must be the format char */
        format_char = format[format_len - 1];
 
@@ -456,20 +481,22 @@ PyOS_ascii_formatd(char       *buffer,
        return buffer;
 }
 
-PyAPI_FUNC(char *) PyOS_double_to_string(double val,
-                                         char format_code,
-                                         int precision,
-                                         int flags,
-                                         int *type)
+PyAPI_FUNC(void)
+_PyOS_double_to_string(char *buf, size_t buf_len, double val,
+                   char format_code, int precision,
+                   int flags, int *ptype)
 {
-       char buf[128];
        char format[32];
-       Py_ssize_t len;
-       char *result;
-       char *p;
        int t;
        int upper = 0;
 
+       if (buf_len < 1) {
+               assert(0);
+               /* There's no way to signal this error. Just return. */
+               return;
+       }
+       buf[0] = 0;
+
        /* Validate format_code, and map upper and lower case */
        switch (format_code) {
        case 'e':          /* exponent */
@@ -490,25 +517,29 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
                break;
        case 'r':          /* repr format */
                /* Supplied precision is unused, must be 0. */
-               if (precision != 0) {
-                       PyErr_BadInternalCall();
-                       return NULL;
-               }
+               if (precision != 0)
+                       return;
                precision = 17;
                format_code = 'g';
                break;
        case 's':          /* str format */
                /* Supplied precision is unused, must be 0. */
-               if (precision != 0) {
-                       PyErr_BadInternalCall();
-                       return NULL;
-               }
+               if (precision != 0)
+                       return;
                precision = 12;
                format_code = 'g';
                break;
        default:
-               PyErr_BadInternalCall();
-               return NULL;
+               assert(0);
+               return;
+       }
+
+       /* Check for buf too small to fit "-inf". Other buffer too small
+          conditions are dealt with when converting or formatting finite
+          numbers. */
+       if (buf_len < 5) {
+               assert(0);
+               return;
        }
 
        /* Handle nan and inf. */
@@ -524,41 +555,74 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
        } else {
                t = Py_DTST_FINITE;
 
+               /* Build the format string. */
+               PyOS_snprintf(format, sizeof(format), "%%%s.%i%c",
+                             (flags & Py_DTSF_ALT ? "#" : ""), precision,
+                             format_code);
 
-               if (flags & Py_DTSF_ADD_DOT_0)
-                       format_code = 'Z';
+               /* Have PyOS_snprintf do the hard work. */
+               PyOS_snprintf(buf, buf_len, format, val);
 
-               PyOS_snprintf(format, 32, "%%%s.%i%c", (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code);
-               PyOS_ascii_formatd(buf, sizeof(buf), format, val);
-       }
+               /* Do various fixups on the return string */
 
-       len = strlen(buf);
+               /* Get the current locale, and find the decimal point string.
+                  Convert that string back to a dot. */
+               change_decimal_from_locale_to_dot(buf);
 
-       /* Add 1 for the trailing 0 byte.
-          Add 1 because we might need to make room for the sign.
-          */
-       result = PyMem_Malloc(len + 2);
-       if (result == NULL) {
-               PyErr_NoMemory();
-               return NULL;
+               /* If an exponent exists, ensure that the exponent is at least
+                  MIN_EXPONENT_DIGITS digits, providing the buffer is large
+                  enough for the extra zeros.  Also, if there are more than
+                  MIN_EXPONENT_DIGITS, remove as many zeros as possible until
+                  we get back to MIN_EXPONENT_DIGITS */
+               ensure_minumim_exponent_length(buf, buf_len);
+
+               /* Possibly make sure we have at least one character after the
+                  decimal point (and make sure we have a decimal point). */
+               if (flags & Py_DTSF_ADD_DOT_0)
+                       ensure_decimal_point(buf, buf_len);
        }
-       p = result;
 
-       /* Add sign when requested.  It's convenient (esp. when formatting
-        complex numbers) to include a sign even for inf and nan. */
+       /* Add the sign if asked and the result isn't negative. */
        if (flags & Py_DTSF_SIGN && buf[0] != '-')
-               *p++ = '+';
-
-       strcpy(p, buf);
+               ensure_sign(buf, buf_len);
 
        if (upper) {
                /* Convert to upper case. */
-               char *p1;
-               for (p1 = p; *p1; p1++)
-                       *p1 = toupper(*p1);
+               char *p;
+               for (p = buf; *p; p++)
+                       *p = toupper(*p);
+       }
+
+       if (ptype)
+               *ptype = t;
+}
+
+
+PyAPI_FUNC(char *) PyOS_double_to_string(double val,
+                                         char format_code,
+                                         int precision,
+                                         int flags,
+                                         int *ptype)
+{
+       char buf[128];
+       Py_ssize_t len;
+       char *result;
+
+       _PyOS_double_to_string(buf, sizeof(buf), val, format_code, precision,
+                              flags, ptype);
+       len = strlen(buf);
+       if (len == 0) {
+               PyErr_BadInternalCall();
+               return NULL;
+       }
+
+       /* Add 1 for the trailing 0 byte. */
+       result = PyMem_Malloc(len + 1);
+       if (result == NULL) {
+               PyErr_NoMemory();
+               return NULL;
        }
+       strcpy(result, buf);
 
-       if (type)
-               *type = t;
        return result;
 }