summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/conversion.rst35
-rw-r--r--Include/pystrtod.h12
-rw-r--r--Lib/test/test_ascii_formatd.py62
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/cPickle.c3
-rw-r--r--Objects/floatobject.c5
-rw-r--r--Objects/stringobject.c9
-rw-r--r--Objects/unicodeobject.c15
-rw-r--r--Python/pystrtod.c154
9 files changed, 232 insertions, 66 deletions
diff --git a/Doc/c-api/conversion.rst b/Doc/c-api/conversion.rst
index 0c81bc0..e391b76 100644
--- a/Doc/c-api/conversion.rst
+++ b/Doc/c-api/conversion.rst
@@ -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)
diff --git a/Include/pystrtod.h b/Include/pystrtod.h
index 1caa7ae..106448b 100644
--- a/Include/pystrtod.h
+++ b/Include/pystrtod.h
@@ -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
index 0000000..3501955
--- /dev/null
+++ b/Lib/test/test_ascii_formatd.py
@@ -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()
diff --git a/Misc/NEWS b/Misc/NEWS
index 7c36bda..b211512 100644
--- 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
diff --git a/Modules/cPickle.c b/Modules/cPickle.c
index fb13ba1..6c7ed99 100644
--- a/Modules/cPickle.c
+++ b/Modules/cPickle.c
@@ -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");
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 4f041f4..382b991 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -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++;
diff --git a/Objects/stringobject.c b/Objects/stringobject.c
index 89614e6..316a271a 100644
--- a/Objects/stringobject.c
+++ b/Objects/stringobject.c
@@ -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);
}
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 6edc2f8..62191ad 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -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*
diff --git a/Python/pystrtod.c b/Python/pystrtod.c
index 703ae64..f9a8831 100644
--- a/Python/pystrtod.c
+++ b/Python/pystrtod.c
@@ -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;
}