Issue #1588: Add complex.__format__.

This commit is contained in:
Eric Smith 2009-04-30 01:00:33 +00:00
parent 738a41dd85
commit 58a42244cf
6 changed files with 425 additions and 52 deletions

View File

@ -54,6 +54,12 @@ PyAPI_FUNC(double) PyComplex_RealAsDouble(PyObject *op);
PyAPI_FUNC(double) PyComplex_ImagAsDouble(PyObject *op); PyAPI_FUNC(double) PyComplex_ImagAsDouble(PyObject *op);
PyAPI_FUNC(Py_complex) PyComplex_AsCComplex(PyObject *op); PyAPI_FUNC(Py_complex) PyComplex_AsCComplex(PyObject *op);
/* Format the object based on the format_spec, as defined in PEP 3101
(Advanced String Formatting). */
PyAPI_FUNC(PyObject *) _PyComplex_FormatAdvanced(PyObject *obj,
Py_UNICODE *format_spec,
Py_ssize_t format_spec_len);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -436,7 +436,66 @@ class ComplexTest(unittest.TestCase):
self.assertFloatsAreIdentical(0.0 + z.imag, self.assertFloatsAreIdentical(0.0 + z.imag,
0.0 + roundtrip.imag) 0.0 + roundtrip.imag)
def test_format(self):
# empty format string is same as str()
self.assertEqual(format(1+3j, ''), str(1+3j))
self.assertEqual(format(1.5+3.5j, ''), str(1.5+3.5j))
self.assertEqual(format(3j, ''), str(3j))
self.assertEqual(format(3.2j, ''), str(3.2j))
self.assertEqual(format(3+0j, ''), str(3+0j))
self.assertEqual(format(3.2+0j, ''), str(3.2+0j))
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')
self.assertEqual(format(1.5+3.5j, '+g'), '+1.5+3.5j')
self.assertEqual(format(1.5-3.5j, '+g'), '+1.5-3.5j')
self.assertEqual(format(1.5-3.5j, '-g'), '1.5-3.5j')
self.assertEqual(format(1.5+3.5j, ' g'), ' 1.5+3.5j')
self.assertEqual(format(1.5-3.5j, ' g'), ' 1.5-3.5j')
self.assertEqual(format(-1.5+3.5j, ' g'), '-1.5+3.5j')
self.assertEqual(format(-1.5-3.5j, ' g'), '-1.5-3.5j')
self.assertEqual(format(-1.5-3.5e-20j, 'g'), '-1.5-3.5e-20j')
self.assertEqual(format(-1.5-3.5j, 'f'), '-1.500000-3.500000j')
self.assertEqual(format(-1.5-3.5j, 'F'), '-1.500000-3.500000j')
self.assertEqual(format(-1.5-3.5j, 'e'), '-1.500000e+00-3.500000e+00j')
self.assertEqual(format(-1.5-3.5j, '.2e'), '-1.50e+00-3.50e+00j')
self.assertEqual(format(-1.5-3.5j, '.2E'), '-1.50E+00-3.50E+00j')
self.assertEqual(format(-1.5e10-3.5e5j, '.2G'), '-1.5E+10-3.5E+05j')
self.assertEqual(format(1.5+3j, '<20g'), '1.5+3j ')
self.assertEqual(format(1.5+3j, '*<20g'), '1.5+3j**************')
self.assertEqual(format(1.5+3j, '>20g'), ' 1.5+3j')
self.assertEqual(format(1.5+3j, '^20g'), ' 1.5+3j ')
self.assertEqual(format(1.5+3j, '<20'), '(1.5+3j) ')
self.assertEqual(format(1.5+3j, '>20'), ' (1.5+3j)')
self.assertEqual(format(1.5+3j, '^20'), ' (1.5+3j) ')
self.assertEqual(format(1.123-3.123j, '^20.2'), ' (1.1-3.1j) ')
self.assertEqual(format(1.5+3j, '<20.2f'), '1.50+3.00j ')
self.assertEqual(format(1.5e20+3j, '<20.2f'), '150000000000000000000.00+3.00j')
self.assertEqual(format(1.5e20+3j, '>40.2f'), ' 150000000000000000000.00+3.00j')
self.assertEqual(format(1.5e20+3j, '^40,.2f'), ' 150,000,000,000,000,000,000.00+3.00j ')
self.assertEqual(format(1.5e21+3j, '^40,.2f'), ' 1,500,000,000,000,000,000,000.00+3.00j ')
self.assertEqual(format(1.5e21+3000j, ',.2f'), '1,500,000,000,000,000,000,000.00+3,000.00j')
# alternate is invalid
self.assertRaises(ValueError, (1.5+0.5j).__format__, '#f')
# zero padding is invalid
self.assertRaises(ValueError, (1.5+0.5j).__format__, '010f')
# '=' alignment is invalid
self.assertRaises(ValueError, (1.5+3j).__format__, '=20')
# integer presentation types are an error
for t in 'bcdoxX':
self.assertRaises(ValueError, (1.5+0.5j).__format__, t)
# make sure everything works in ''.format()
self.assertEqual('*{0:.3f}*'.format(3.14159+2.71828j), '*3.142+2.718j*')
def test_main(): def test_main():
support.run_unittest(ComplexTest) support.run_unittest(ComplexTest)

View File

@ -12,6 +12,9 @@ What's New in Python 3.1 beta 1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #1588: Add complex.__format__. For example,
format(complex(1, 2./3), '.5') now produces a sensible result.
- Issue #5864: Fix empty format code formatting for floats so that it - Issue #5864: Fix empty format code formatting for floats so that it
never gives more than the requested number of significant digits. never gives more than the requested number of significant digits.

View File

@ -681,6 +681,23 @@ complex_getnewargs(PyComplexObject *v)
return Py_BuildValue("(dd)", c.real, c.imag); return Py_BuildValue("(dd)", c.real, c.imag);
} }
PyDoc_STRVAR(complex__format__doc,
"complex.__format__() -> str\n"
"\n"
"Converts to a string according to format_spec.");
static PyObject *
complex__format__(PyObject* self, PyObject* args)
{
PyObject *format_spec;
if (!PyArg_ParseTuple(args, "U:__format__", &format_spec))
return NULL;
return _PyComplex_FormatAdvanced(self,
PyUnicode_AS_UNICODE(format_spec),
PyUnicode_GET_SIZE(format_spec));
}
#if 0 #if 0
static PyObject * static PyObject *
complex_is_finite(PyObject *self) complex_is_finite(PyObject *self)
@ -705,6 +722,8 @@ static PyMethodDef complex_methods[] = {
complex_is_finite_doc}, complex_is_finite_doc},
#endif #endif
{"__getnewargs__", (PyCFunction)complex_getnewargs, METH_NOARGS}, {"__getnewargs__", (PyCFunction)complex_getnewargs, METH_NOARGS},
{"__format__", (PyCFunction)complex__format__,
METH_VARARGS, complex__format__doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };

View File

@ -11,6 +11,7 @@
FORMAT_STRING FORMAT_STRING
FORMAT_LONG FORMAT_LONG
FORMAT_FLOAT FORMAT_FLOAT
FORMAT_COMPLEX
to be whatever you want the public names of these functions to to be whatever you want the public names of these functions to
be. These are the only non-static functions defined here. be. These are the only non-static functions defined here.
*/ */
@ -261,7 +262,54 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
return 1; return 1;
} }
#if defined FORMAT_FLOAT || defined FORMAT_LONG /* Calculate the padding needed. */
static void
calc_padding(Py_ssize_t nchars, Py_ssize_t width, STRINGLIB_CHAR align,
Py_ssize_t *n_lpadding, Py_ssize_t *n_rpadding,
Py_ssize_t *n_total)
{
if (width >= 0) {
if (nchars > width)
*n_total = nchars;
else
*n_total = width;
}
else {
/* not specified, use all of the chars and no more */
*n_total = nchars;
}
/* figure out how much leading space we need, based on the
aligning */
if (align == '>')
*n_lpadding = *n_total - nchars;
else if (align == '^')
*n_lpadding = (*n_total - nchars) / 2;
else
*n_lpadding = 0;
*n_rpadding = *n_total - nchars - *n_lpadding;
}
/* Do the padding, and return a pointer to where the caller-supplied
content goes. */
static STRINGLIB_CHAR *
fill_padding(STRINGLIB_CHAR *p, Py_ssize_t nchars, STRINGLIB_CHAR fill_char,
Py_ssize_t n_lpadding, Py_ssize_t n_rpadding)
{
/* Pad on left. */
if (n_lpadding)
STRINGLIB_FILL(p, fill_char, n_lpadding);
/* Pad on right. */
if (n_rpadding)
STRINGLIB_FILL(p + nchars + n_lpadding, fill_char, n_rpadding);
/* Pointer to the user content. */
return p + n_lpadding;
}
#if defined FORMAT_FLOAT || defined FORMAT_LONG || defined FORMAT_COMPLEX
/************************************************************************/ /************************************************************************/
/*********** common routines for numeric formatting *********************/ /*********** common routines for numeric formatting *********************/
/************************************************************************/ /************************************************************************/
@ -304,6 +352,7 @@ typedef struct {
the n_grouped_digits width. */ the n_grouped_digits width. */
} NumberFieldWidths; } NumberFieldWidths;
/* Given a number of the form: /* Given a number of the form:
digits[remainder] digits[remainder]
where ptr points to the start and end points to the end, find where where ptr points to the start and end points to the end, find where
@ -564,7 +613,7 @@ get_locale_info(int type, LocaleInfo *locale_info)
} }
} }
#endif /* FORMAT_FLOAT || FORMAT_LONG */ #endif /* FORMAT_FLOAT || FORMAT_LONG || FORMAT_COMPLEX */
/************************************************************************/ /************************************************************************/
/*********** string formatting ******************************************/ /*********** string formatting ******************************************/
@ -573,10 +622,10 @@ get_locale_info(int type, LocaleInfo *locale_info)
static PyObject * static PyObject *
format_string_internal(PyObject *value, const InternalFormatSpec *format) format_string_internal(PyObject *value, const InternalFormatSpec *format)
{ {
Py_ssize_t width; /* total field width */
Py_ssize_t lpad; Py_ssize_t lpad;
STRINGLIB_CHAR *dst; Py_ssize_t rpad;
STRINGLIB_CHAR *src = STRINGLIB_STR(value); Py_ssize_t total;
STRINGLIB_CHAR *p;
Py_ssize_t len = STRINGLIB_LEN(value); Py_ssize_t len = STRINGLIB_LEN(value);
PyObject *result = NULL; PyObject *result = NULL;
@ -609,56 +658,20 @@ format_string_internal(PyObject *value, const InternalFormatSpec *format)
len = format->precision; len = format->precision;
} }
if (format->width >= 0) { calc_padding(len, format->width, format->align, &lpad, &rpad, &total);
width = format->width;
/* but use at least len characters */
if (len > width) {
width = len;
}
}
else {
/* not specified, use all of the chars and no more */
width = len;
}
/* allocate the resulting string */ /* allocate the resulting string */
result = STRINGLIB_NEW(NULL, width); result = STRINGLIB_NEW(NULL, total);
if (result == NULL) if (result == NULL)
goto done; goto done;
/* now write into that space */ /* Write into that space. First the padding. */
dst = STRINGLIB_STR(result); p = fill_padding(STRINGLIB_STR(result), len,
format->fill_char=='\0'?' ':format->fill_char,
lpad, rpad);
/* figure out how much leading space we need, based on the /* Then the source string. */
aligning */ memcpy(p, STRINGLIB_STR(value), len * sizeof(STRINGLIB_CHAR));
if (format->align == '>')
lpad = width - len;
else if (format->align == '^')
lpad = (width - len) / 2;
else
lpad = 0;
/* if right aligning, increment the destination allow space on the
left */
memcpy(dst + lpad, src, len * sizeof(STRINGLIB_CHAR));
/* do any padding */
if (width > len) {
STRINGLIB_CHAR fill_char = format->fill_char;
if (fill_char == '\0') {
/* use the default, if not specified */
fill_char = ' ';
}
/* pad on left */
if (lpad)
STRINGLIB_FILL(dst, fill_char, lpad);
/* pad on right */
if (width - len - lpad)
STRINGLIB_FILL(dst + len + lpad, fill_char, width - len - lpad);
}
done: done:
return result; return result;
@ -997,6 +1010,231 @@ done:
} }
#endif /* FORMAT_FLOAT */ #endif /* FORMAT_FLOAT */
/************************************************************************/
/*********** complex formatting *****************************************/
/************************************************************************/
#ifdef FORMAT_COMPLEX
static PyObject *
format_complex_internal(PyObject *value,
const InternalFormatSpec *format)
{
double re;
double im;
char *re_buf = NULL; /* buffer returned from PyOS_double_to_string */
char *im_buf = NULL; /* buffer returned from PyOS_double_to_string */
InternalFormatSpec tmp_format = *format;
Py_ssize_t n_re_digits;
Py_ssize_t n_im_digits;
Py_ssize_t n_re_remainder;
Py_ssize_t n_im_remainder;
Py_ssize_t n_re_total;
Py_ssize_t n_im_total;
int re_has_decimal;
int im_has_decimal;
Py_ssize_t precision = format->precision;
STRINGLIB_CHAR type = format->type;
STRINGLIB_CHAR *p_re;
STRINGLIB_CHAR *p_im;
NumberFieldWidths re_spec;
NumberFieldWidths im_spec;
int flags = 0;
PyObject *result = NULL;
STRINGLIB_CHAR *p;
STRINGLIB_CHAR re_sign_char = '\0';
STRINGLIB_CHAR im_sign_char = '\0';
int re_float_type; /* Used to see if we have a nan, inf, or regular float. */
int im_float_type;
int add_parens = 0;
int skip_re = 0;
Py_ssize_t lpad;
Py_ssize_t rpad;
Py_ssize_t total;
#if STRINGLIB_IS_UNICODE
Py_UNICODE *re_unicode_tmp = NULL;
Py_UNICODE *im_unicode_tmp = NULL;
#endif
/* Locale settings, either from the actual locale or
from a hard-code pseudo-locale */
LocaleInfo locale;
/* Alternate is not allowed on complex. */
if (format->alternate) {
PyErr_SetString(PyExc_ValueError,
"Alternate form (#) not allowed in complex format "
"specifier");
goto done;
}
/* Neither is zero pading. */
if (format->fill_char == '0') {
PyErr_SetString(PyExc_ValueError,
"Zero padding is not allowed in complex format "
"specifier");
goto done;
}
/* Neither is '=' alignment . */
if (format->align == '=') {
PyErr_SetString(PyExc_ValueError,
"'=' alignment flag is not allowed in complex format "
"specifier");
goto done;
}
re = PyComplex_RealAsDouble(value);
if (re == -1.0 && PyErr_Occurred())
goto done;
im = PyComplex_ImagAsDouble(value);
if (im == -1.0 && PyErr_Occurred())
goto done;
if (type == '\0') {
/* Omitted type specifier. Should be like str(self). */
type = 'g';
add_parens = 1;
if (re == 0.0)
skip_re = 1;
}
if (type == 'n')
/* 'n' is the same as 'g', except for the locale used to
format the result. We take care of that later. */
type = 'g';
/* 'F' is the same as 'f', per the PEP */
if (type == 'F')
type = 'f';
if (precision < 0)
precision = 6;
/* 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"
can be. */
re_buf = PyOS_double_to_string(re, (char)type, precision, flags,
&re_float_type);
if (re_buf == NULL)
goto done;
im_buf = PyOS_double_to_string(im, (char)type, precision, flags,
&im_float_type);
if (im_buf == NULL)
goto done;
n_re_digits = strlen(re_buf);
n_im_digits = strlen(im_buf);
/* Since there is no unicode version of PyOS_double_to_string,
just use the 8 bit version and then convert to unicode. */
#if STRINGLIB_IS_UNICODE
re_unicode_tmp = (Py_UNICODE*)PyMem_Malloc((n_re_digits)*sizeof(Py_UNICODE));
if (re_unicode_tmp == NULL) {
PyErr_NoMemory();
goto done;
}
strtounicode(re_unicode_tmp, re_buf, n_re_digits);
p_re = re_unicode_tmp;
im_unicode_tmp = (Py_UNICODE*)PyMem_Malloc((n_im_digits)*sizeof(Py_UNICODE));
if (im_unicode_tmp == NULL) {
PyErr_NoMemory();
goto done;
}
strtounicode(im_unicode_tmp, im_buf, n_im_digits);
p_im = im_unicode_tmp;
#else
p_re = re_buf;
p_im = im_buf;
#endif
/* Is a sign character present in the output? If so, remember it
and skip it */
if (*p_re == '-') {
re_sign_char = *p_re;
++p_re;
--n_re_digits;
}
if (*p_im == '-') {
im_sign_char = *p_im;
++p_im;
--n_im_digits;
}
/* Determine if we have any "remainder" (after the digits, might include
decimal or exponent or both (or neither)) */
parse_number(p_re, n_re_digits, &n_re_remainder, &re_has_decimal);
parse_number(p_im, n_im_digits, &n_im_remainder, &im_has_decimal);
/* Determine the grouping, separator, and decimal point, if any. */
get_locale_info(format->type == 'n' ? LT_CURRENT_LOCALE :
(format->thousands_separators ?
LT_DEFAULT_LOCALE :
LT_NO_LOCALE),
&locale);
/* Turn off any padding. We'll do it later after we've composed
the numbers without padding. */
tmp_format.fill_char = '\0';
tmp_format.align = '\0';
tmp_format.width = -1;
/* Calculate how much memory we'll need. */
n_re_total = calc_number_widths(&re_spec, 0, re_sign_char, p_re,
n_re_digits, n_re_remainder,
re_has_decimal, &locale, &tmp_format);
/* Same formatting, but always include a sign. */
tmp_format.sign = '+';
n_im_total = calc_number_widths(&im_spec, 0, im_sign_char, p_im,
n_im_digits, n_im_remainder,
im_has_decimal, &locale, &tmp_format);
if (skip_re)
n_re_total = 0;
/* Add 1 for the 'j', and optionally 2 for parens. */
calc_padding(n_re_total + n_im_total + 1 + add_parens * 2,
format->width, format->align, &lpad, &rpad, &total);
result = STRINGLIB_NEW(NULL, total);
if (result == NULL)
goto done;
/* Populate the memory. First, the padding. */
p = fill_padding(STRINGLIB_STR(result),
n_re_total + n_im_total + 1 + add_parens * 2,
format->fill_char=='\0' ? ' ' : format->fill_char,
lpad, rpad);
if (add_parens)
*p++ = '(';
if (!skip_re) {
fill_number(p, &re_spec, p_re, n_re_digits, NULL, 0, &locale, 0);
p += n_re_total;
}
fill_number(p, &im_spec, p_im, n_im_digits, NULL, 0, &locale, 0);
p += n_im_total;
*p++ = 'j';
if (add_parens)
*p++ = ')';
done:
PyMem_Free(re_buf);
PyMem_Free(im_buf);
#if STRINGLIB_IS_UNICODE
PyMem_Free(re_unicode_tmp);
PyMem_Free(im_unicode_tmp);
#endif
return result;
}
#endif /* FORMAT_COMPLEX */
/************************************************************************/ /************************************************************************/
/*********** built in formatters ****************************************/ /*********** built in formatters ****************************************/
/************************************************************************/ /************************************************************************/
@ -1196,3 +1434,50 @@ done:
return result; return result;
} }
#endif /* FORMAT_FLOAT */ #endif /* FORMAT_FLOAT */
#ifdef FORMAT_COMPLEX
PyObject *
FORMAT_COMPLEX(PyObject *obj,
STRINGLIB_CHAR *format_spec,
Py_ssize_t format_spec_len)
{
PyObject *result = NULL;
InternalFormatSpec format;
/* check for the special case of zero length format spec, make
it equivalent to str(obj) */
if (format_spec_len == 0) {
result = STRINGLIB_TOSTR(obj);
goto done;
}
/* parse the format_spec */
if (!parse_internal_render_format_spec(format_spec,
format_spec_len,
&format, '\0'))
goto done;
/* type conversion? */
switch (format.type) {
case '\0': /* No format code: like 'g', but with at least one decimal. */
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'n':
/* no conversion, already a complex. do the formatting */
result = format_complex_internal(obj, &format);
break;
default:
/* unknown */
unknown_presentation_type(format.type, obj->ob_type->tp_name);
goto done;
}
done:
return result;
}
#endif /* FORMAT_COMPLEX */

View File

@ -6,8 +6,9 @@
#include "../Objects/stringlib/unicodedefs.h" #include "../Objects/stringlib/unicodedefs.h"
#define FORMAT_STRING _PyUnicode_FormatAdvanced #define FORMAT_STRING _PyUnicode_FormatAdvanced
#define FORMAT_LONG _PyLong_FormatAdvanced #define FORMAT_LONG _PyLong_FormatAdvanced
#define FORMAT_FLOAT _PyFloat_FormatAdvanced #define FORMAT_FLOAT _PyFloat_FormatAdvanced
#define FORMAT_COMPLEX _PyComplex_FormatAdvanced
#include "../Objects/stringlib/formatter.h" #include "../Objects/stringlib/formatter.h"