Issue #5835, deprecate PyOS_ascii_formatd.

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.
This commit is contained in:
Eric Smith 2009-04-25 21:40:15 +00:00
parent dfcffd4044
commit 068f06568b
9 changed files with 234 additions and 68 deletions

View file

@ -76,7 +76,42 @@ The following functions provide locale-independent string to number conversions.
the conversion failed. the conversion failed.
.. versionadded:: 2.4 .. 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) .. cfunction:: double PyOS_ascii_atof(const char *nptr)

View file

@ -8,7 +8,17 @@ extern "C" {
PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr); PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr);
PyAPI_FUNC(double) PyOS_ascii_atof(const char *str); 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 /* The caller is responsible for calling PyMem_Free to free the buffer
that's is returned. */ that's is returned. */

View file

@ -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()

View file

@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
Core and Builtins 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 #5283: Setting __class__ in __del__ caused a segfault.
- Issue #5816: complex(repr(z)) now recovers z exactly, even when - Issue #5816: complex(repr(z)) now recovers z exactly, even when

View file

@ -1166,7 +1166,8 @@ save_float(Picklerobject *self, PyObject *args)
else { else {
char c_str[250]; char c_str[250];
c_str[0] = FLOAT; 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 */ /* Extend the formatted string with a newline character */
strcat(c_str, "\n"); strcat(c_str, "\n");

View file

@ -342,7 +342,6 @@ static void
format_float(char *buf, size_t buflen, PyFloatObject *v, int precision) format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
{ {
register char *cp; register char *cp;
char format[32];
int i; int i;
/* Subroutine for float_repr and float_print. /* 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. */ in such cases, we append ".0" to the string. */
assert(PyFloat_Check(v)); assert(PyFloat_Check(v));
PyOS_snprintf(format, 32, "%%.%ig", precision); _PyOS_double_to_string(buf, buflen, v->ob_fval, 'g', precision,
PyOS_ascii_formatd(buf, buflen, format, v->ob_fval); 0, NULL);
cp = buf; cp = buf;
if (*cp == '-') if (*cp == '-')
cp++; cp++;

View file

@ -4332,9 +4332,6 @@ Py_LOCAL_INLINE(int)
formatfloat(char *buf, size_t buflen, int flags, formatfloat(char *buf, size_t buflen, int flags,
int prec, int type, PyObject *v) 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; double x;
x = PyFloat_AsDouble(v); x = PyFloat_AsDouble(v);
if (x == -1.0 && PyErr_Occurred()) { 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?)"); "formatted float is too long (precision too large?)");
return -1; return -1;
} }
PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c", _PyOS_double_to_string(buf, buflen, x, type, prec,
(flags&F_ALT) ? "#" : "", (flags&F_ALT)?Py_DTSF_ALT:0, NULL);
prec, type);
PyOS_ascii_formatd(buf, buflen, fmt, x);
return (int)strlen(buf); return (int)strlen(buf);
} }

View file

@ -8245,11 +8245,13 @@ strtounicode(Py_UNICODE *buffer, const char *charbuffer)
} }
static int 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; 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); result = strtounicode(buffer, (char *)buffer);
return Py_SAFE_DOWNCAST(result, Py_ssize_t, int); return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
} }
@ -8276,9 +8278,6 @@ formatfloat(Py_UNICODE *buf,
int type, int type,
PyObject *v) PyObject *v)
{ {
/* fmt = '%#.' + `prec` + `type`
worst case length = 3 + 10 (len of INT_MAX) + 1 = 14 (use 20)*/
char fmt[20];
double x; double x;
x = PyFloat_AsDouble(v); x = PyFloat_AsDouble(v);
@ -8320,10 +8319,8 @@ formatfloat(Py_UNICODE *buf,
"formatted float is too long (precision too large?)"); "formatted float is too long (precision too large?)");
return -1; return -1;
} }
PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c", return doubletounicode(buf, buflen, type, prec,
(flags&F_ALT) ? "#" : "", (flags&F_ALT)?Py_DTSF_ALT:0, x);
prec, type);
return doubletounicode(buf, buflen, fmt, x);
} }
static PyObject* static PyObject*

View 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: /* From the C99 standard, section 7.19.6:
The exponent always contains at least two digits, and only as many more digits The exponent always contains at least two digits, and only as many more digits
as necessary to represent the exponent. as necessary to represent the exponent.
@ -363,7 +382,7 @@ ensure_decimal_point(char* buffer, size_t buf_size)
#define FLOAT_FORMATBUFLEN 120 #define FLOAT_FORMATBUFLEN 120
/** /**
* PyOS_ascii_formatd: * _PyOS_ascii_formatd:
* @buffer: A buffer to place the resulting string in * @buffer: A buffer to place the resulting string in
* @buf_size: The length of the buffer. * @buf_size: The length of the buffer.
* @format: The printf()-style format to use for the * @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. * 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, PyOS_ascii_formatd(char *buffer,
size_t buf_size, size_t buf_size,
const char *format, const char *format,
@ -393,6 +413,11 @@ PyOS_ascii_formatd(char *buffer,
also with at least one character past the decimal. */ also with at least one character past the decimal. */
char tmp_format[FLOAT_FORMATBUFLEN]; 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 */ /* The last character in the format string must be the format char */
format_char = format[format_len - 1]; format_char = format[format_len - 1];
@ -456,20 +481,22 @@ PyOS_ascii_formatd(char *buffer,
return buffer; return buffer;
} }
PyAPI_FUNC(char *) PyOS_double_to_string(double val, PyAPI_FUNC(void)
char format_code, _PyOS_double_to_string(char *buf, size_t buf_len, double val,
int precision, char format_code, int precision,
int flags, int flags, int *ptype)
int *type)
{ {
char buf[128];
char format[32]; char format[32];
Py_ssize_t len;
char *result;
char *p;
int t; int t;
int upper = 0; 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 */ /* Validate format_code, and map upper and lower case */
switch (format_code) { switch (format_code) {
case 'e': /* exponent */ case 'e': /* exponent */
@ -490,25 +517,29 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
break; break;
case 'r': /* repr format */ case 'r': /* repr format */
/* Supplied precision is unused, must be 0. */ /* Supplied precision is unused, must be 0. */
if (precision != 0) { if (precision != 0)
PyErr_BadInternalCall(); return;
return NULL;
}
precision = 17; precision = 17;
format_code = 'g'; format_code = 'g';
break; break;
case 's': /* str format */ case 's': /* str format */
/* Supplied precision is unused, must be 0. */ /* Supplied precision is unused, must be 0. */
if (precision != 0) { if (precision != 0)
PyErr_BadInternalCall(); return;
return NULL;
}
precision = 12; precision = 12;
format_code = 'g'; format_code = 'g';
break; break;
default: default:
PyErr_BadInternalCall(); assert(0);
return NULL; 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. */ /* Handle nan and inf. */
@ -524,41 +555,74 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
} else { } else {
t = Py_DTST_FINITE; t = Py_DTST_FINITE;
/* Build the format string. */
PyOS_snprintf(format, sizeof(format), "%%%s.%i%c",
(flags & Py_DTSF_ALT ? "#" : ""), precision,
format_code);
/* Have PyOS_snprintf do the hard work. */
PyOS_snprintf(buf, buf_len, format, val);
/* Do various fixups on the return string */
/* Get the current locale, and find the decimal point string.
Convert that string back to a dot. */
change_decimal_from_locale_to_dot(buf);
/* 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) if (flags & Py_DTSF_ADD_DOT_0)
format_code = 'Z'; ensure_decimal_point(buf, buf_len);
PyOS_snprintf(format, 32, "%%%s.%i%c", (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code);
PyOS_ascii_formatd(buf, sizeof(buf), format, val);
} }
len = strlen(buf); /* Add the sign if asked and the result isn't negative. */
if (flags & Py_DTSF_SIGN && buf[0] != '-')
ensure_sign(buf, buf_len);
/* Add 1 for the trailing 0 byte. if (upper) {
Add 1 because we might need to make room for the sign. /* Convert to upper case. */
*/ char *p;
result = PyMem_Malloc(len + 2); 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) { if (result == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
return NULL; return NULL;
} }
p = result; strcpy(result, buf);
/* Add sign when requested. It's convenient (esp. when formatting
complex numbers) to include a sign even for inf and nan. */
if (flags & Py_DTSF_SIGN && buf[0] != '-')
*p++ = '+';
strcpy(p, buf);
if (upper) {
/* Convert to upper case. */
char *p1;
for (p1 = p; *p1; p1++)
*p1 = toupper(*p1);
}
if (type)
*type = t;
return result; return result;
} }