Added ',' thousands grouping to int.__format__. See PEP 378.

This is incomplete, but I want to get some version into the next alpha. I am still working on:
Documentation.
More tests.
Implement for floats.

In addition, there's an existing bug with 'n' formatting that carries forward to thousands grouping (issue 5515).
This commit is contained in:
Eric Smith 2009-04-03 14:45:06 +00:00
parent f8c8b6d3ea
commit a3b1ac8dca
10 changed files with 190 additions and 88 deletions

View file

@ -91,13 +91,25 @@ PyAPI_FUNC(int) PyBytes_AsStringAndSize(
into the string pointed to by buffer. For the argument descriptions, into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */ see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer, PyAPI_FUNC(int) _PyBytes_InsertThousandsGroupingLocale(char *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char); int append_zero_char);
/* Using explicit passed-in values, insert the thousands grouping
into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char,
const char *grouping,
const char *thousands_sep);
/* Flags used by string formatting */ /* Flags used by string formatting */
#define F_LJUST (1<<0) #define F_LJUST (1<<0)
#define F_SIGN (1<<1) #define F_SIGN (1<<1)

View file

@ -1482,13 +1482,24 @@ PyAPI_FUNC(PyObject *) _PyUnicode_XStrip(
into the string pointed to by buffer. For the argument descriptions, into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */ see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer, PyAPI_FUNC(int) _PyUnicode_InsertThousandsGroupingLocale(Py_UNICODE *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char); int append_zero_char);
/* Using explicit passed-in values, insert the thousands grouping
into the string pointed to by buffer. For the argument descriptions,
see Objects/stringlib/localeutil.h */
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char,
const char *grouping,
const char *thousands_sep);
/* === Characters Type APIs =============================================== */ /* === Characters Type APIs =============================================== */
/* Helper array used by Py_UNICODE_ISSPACE(). */ /* Helper array used by Py_UNICODE_ISSPACE(). */

View file

@ -338,6 +338,15 @@ class TypesTests(unittest.TestCase):
test(123456, "#012X", '0X000001E240') test(123456, "#012X", '0X000001E240')
test(-123456, "#012X", '-0X00001E240') test(-123456, "#012X", '-0X00001E240')
test(123, ',', '123')
test(-123, ',', '-123')
test(1234, ',', '1,234')
test(-1234, ',', '-1,234')
test(123456, ',', '123,456')
test(-123456, ',', '-123,456')
test(1234567, ',', '1,234,567')
test(-1234567, ',', '-1,234,567')
# make sure these are errors # make sure these are errors
# precision disallowed # precision disallowed
@ -347,6 +356,8 @@ class TypesTests(unittest.TestCase):
# format spec must be string # format spec must be string
self.assertRaises(TypeError, 3 .__format__, None) self.assertRaises(TypeError, 3 .__format__, None)
self.assertRaises(TypeError, 3 .__format__, 0) self.assertRaises(TypeError, 3 .__format__, 0)
# can't have ',' with 'n'
self.assertRaises(ValueError, 3 .__format__, ",n")
# ensure that only int and float type specifiers work # ensure that only int and float type specifiers work
for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] + for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] +

View file

@ -583,6 +583,7 @@ PyBytes_AsStringAndSize(register PyObject *obj,
#include "stringlib/transmogrify.h" #include "stringlib/transmogrify.h"
#define _Py_InsertThousandsGrouping _PyBytes_InsertThousandsGrouping #define _Py_InsertThousandsGrouping _PyBytes_InsertThousandsGrouping
#define _Py_InsertThousandsGroupingLocale _PyBytes_InsertThousandsGroupingLocale
#include "stringlib/localeutil.h" #include "stringlib/localeutil.h"
PyObject * PyObject *

View file

@ -120,6 +120,7 @@ typedef struct {
int alternate; int alternate;
STRINGLIB_CHAR sign; STRINGLIB_CHAR sign;
Py_ssize_t width; Py_ssize_t width;
int thousands_separators;
Py_ssize_t precision; Py_ssize_t precision;
STRINGLIB_CHAR type; STRINGLIB_CHAR type;
} InternalFormatSpec; } InternalFormatSpec;
@ -149,6 +150,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
format->alternate = 0; format->alternate = 0;
format->sign = '\0'; format->sign = '\0';
format->width = -1; format->width = -1;
format->thousands_separators = 0;
format->precision = -1; format->precision = -1;
format->type = default_type; format->type = default_type;
@ -201,6 +203,12 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
format->width = -1; format->width = -1;
} }
/* Comma signifies add thousands separators */
if (end-ptr && ptr[0] == ',') {
format->thousands_separators = 1;
++ptr;
}
/* Parse field precision */ /* Parse field precision */
if (end-ptr && ptr[0] == '.') { if (end-ptr && ptr[0] == '.') {
++ptr; ++ptr;
@ -230,6 +238,11 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
++ptr; ++ptr;
} }
if (format->type == 'n' && format->thousands_separators) {
PyErr_Format(PyExc_ValueError, "Cannot specify ',' with 'n'.");
return 0;
}
return 1; return 1;
} }
@ -630,8 +643,13 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
if (format->type == 'n') if (format->type == 'n')
/* Compute how many additional chars we need to allocate /* Compute how many additional chars we need to allocate
to hold the thousands grouping. */ to hold the thousands grouping. */
STRINGLIB_GROUPING(NULL, n_digits, n_digits, STRINGLIB_GROUPING_LOCALE(NULL, n_digits, n_digits,
0, &n_grouping_chars, 0); 0, &n_grouping_chars, 0);
if (format->thousands_separators)
/* Compute how many additional chars we need to allocate
to hold the thousands grouping. */
STRINGLIB_GROUPING(NULL, n_digits, n_digits,
0, &n_grouping_chars, 0, "\3", ",");
/* Calculate the widths of the various leading and trailing parts */ /* Calculate the widths of the various leading and trailing parts */
calc_number_widths(&spec, sign, n_prefix, n_digits + n_grouping_chars, calc_number_widths(&spec, sign, n_prefix, n_digits + n_grouping_chars,
@ -670,11 +688,22 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
reserved enough space. */ reserved enough space. */
STRINGLIB_CHAR *pstart = p + n_leading_chars; STRINGLIB_CHAR *pstart = p + n_leading_chars;
#ifndef NDEBUG #ifndef NDEBUG
int r = int r;
#endif #endif
STRINGLIB_GROUPING(pstart, n_digits, n_digits, if (format->type == 'n')
#ifndef NDEBUG
r =
#endif
STRINGLIB_GROUPING_LOCALE(pstart, n_digits, n_digits,
spec.n_total+n_grouping_chars-n_leading_chars, spec.n_total+n_grouping_chars-n_leading_chars,
NULL, 0); NULL, 0);
else
#ifndef NDEBUG
r =
STRINGLIB_GROUPING(pstart, n_digits, n_digits,
spec.n_total+n_grouping_chars-n_leading_chars,
NULL, 0, "\3", ",");
#endif
assert(r); assert(r);
} }

View file

@ -18,11 +18,13 @@
* @append_zero_char: If non-zero, put a trailing zero at the end of * @append_zero_char: If non-zero, put a trailing zero at the end of
* of the resulting string, if and only if we modified the * of the resulting string, if and only if we modified the
* string. * string.
* @grouping: see definition in localeconv().
* @thousands_sep: see definition in localeconv().
* *
* Inserts thousand grouping characters (as defined in the current * Inserts thousand grouping characters (as defined by grouping and
* locale) into the string between buffer and buffer+n_digits. If * thousands_sep) into the string between buffer and buffer+n_digits.
* count is non-NULL, don't do any formatting, just count the number * If count is non-NULL, don't do any formatting, just count the
* of characters to insert. This is used by the caller to * number of characters to insert. This is used by the caller to
* appropriately resize the buffer, if needed. If count is non-NULL, * appropriately resize the buffer, if needed. If count is non-NULL,
* buffer can be NULL (it is not dereferenced at all in that case). * buffer can be NULL (it is not dereferenced at all in that case).
* *
@ -34,97 +36,130 @@
**/ **/
int int
_Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer, _Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer,
Py_ssize_t n_buffer, Py_ssize_t n_buffer,
Py_ssize_t n_digits, Py_ssize_t n_digits,
Py_ssize_t buf_size, Py_ssize_t buf_size,
Py_ssize_t *count, Py_ssize_t *count,
int append_zero_char) int append_zero_char,
const char *grouping,
const char *thousands_sep)
{ {
struct lconv *locale_data = localeconv(); Py_ssize_t thousands_sep_len = strlen(thousands_sep);
const char *grouping = locale_data->grouping; STRINGLIB_CHAR *pend = NULL; /* current end of buffer */
const char *thousands_sep = locale_data->thousands_sep; STRINGLIB_CHAR *pmax = NULL; /* max of buffer */
Py_ssize_t thousands_sep_len = strlen(thousands_sep); char current_grouping;
STRINGLIB_CHAR *pend = NULL; /* current end of buffer */ Py_ssize_t remaining = n_digits; /* Number of chars remaining to
STRINGLIB_CHAR *pmax = NULL; /* max of buffer */ be looked at */
char current_grouping;
Py_ssize_t remaining = n_digits; /* Number of chars remaining to
be looked at */
/* Initialize the character count, if we're just counting. */ /* Initialize the character count, if we're just counting. */
if (count) if (count)
*count = 0; *count = 0;
else { else {
/* We're not just counting, we're modifying buffer */ /* We're not just counting, we're modifying buffer */
pend = buffer + n_buffer; pend = buffer + n_buffer;
pmax = buffer + buf_size; pmax = buffer + buf_size;
} }
/* Starting at the end and working right-to-left, keep track of /* Starting at the end and working right-to-left, keep track of
what grouping needs to be added and insert that. */ what grouping needs to be added and insert that. */
current_grouping = *grouping++; current_grouping = *grouping++;
/* If the first character is 0, perform no grouping at all. */ /* If the first character is 0, perform no grouping at all. */
if (current_grouping == 0) if (current_grouping == 0)
return 1; return 1;
while (remaining > current_grouping) { while (remaining > current_grouping) {
/* Always leave buffer and pend valid at the end of this /* Always leave buffer and pend valid at the end of this
loop, since we might leave with a return statement. */ loop, since we might leave with a return statement. */
remaining -= current_grouping; remaining -= current_grouping;
if (count) { if (count) {
/* We're only counting, not touching the memory. */ /* We're only counting, not touching the memory. */
*count += thousands_sep_len; *count += thousands_sep_len;
} }
else { else {
/* Do the formatting. */ /* Do the formatting. */
STRINGLIB_CHAR *plast = buffer + remaining; STRINGLIB_CHAR *plast = buffer + remaining;
/* Is there room to insert thousands_sep_len chars? */ /* Is there room to insert thousands_sep_len chars? */
if (pmax - pend < thousands_sep_len) if (pmax - pend < thousands_sep_len)
/* No room. */ /* No room. */
return 0; return 0;
/* Move the rest of the string down. */ /* Move the rest of the string down. */
memmove(plast + thousands_sep_len, memmove(plast + thousands_sep_len,
plast, plast,
(pend - plast) * sizeof(STRINGLIB_CHAR)); (pend - plast) * sizeof(STRINGLIB_CHAR));
/* Copy the thousands_sep chars into the buffer. */ /* Copy the thousands_sep chars into the buffer. */
#if STRINGLIB_IS_UNICODE #if STRINGLIB_IS_UNICODE
/* Convert from the char's of the thousands_sep from /* Convert from the char's of the thousands_sep from
the locale into unicode. */ the locale into unicode. */
{ {
Py_ssize_t i; Py_ssize_t i;
for (i = 0; i < thousands_sep_len; ++i) for (i = 0; i < thousands_sep_len; ++i)
plast[i] = thousands_sep[i]; plast[i] = thousands_sep[i];
} }
#else #else
/* No conversion, just memcpy the thousands_sep. */ /* No conversion, just memcpy the thousands_sep. */
memcpy(plast, thousands_sep, thousands_sep_len); memcpy(plast, thousands_sep, thousands_sep_len);
#endif #endif
} }
/* Adjust end pointer. */ /* Adjust end pointer. */
pend += thousands_sep_len; pend += thousands_sep_len;
/* Move to the next grouping character, unless we're /* Move to the next grouping character, unless we're
repeating (which is designated by a grouping of 0). */ repeating (which is designated by a grouping of 0). */
if (*grouping != 0) { if (*grouping != 0) {
current_grouping = *grouping++; current_grouping = *grouping++;
if (current_grouping == CHAR_MAX) if (current_grouping == CHAR_MAX)
/* We're done. */ /* We're done. */
break; break;
} }
} }
if (append_zero_char) { if (append_zero_char) {
/* Append a zero character to mark the end of the string, /* Append a zero character to mark the end of the string,
if there's room. */ if there's room. */
if (pend - (buffer + remaining) < 1) if (pend - (buffer + remaining) < 1)
/* No room, error. */ /* No room, error. */
return 0; return 0;
*pend = 0; *pend = 0;
} }
return 1; return 1;
}
/**
* _Py_InsertThousandsGroupingLocale:
* @buffer: A pointer to the start of a string.
* @n_buffer: The length of the string.
* @n_digits: The number of digits in the string, in which we want
* to put the grouping chars.
* @buf_size: The maximum size of the buffer pointed to by buffer.
* @count: If non-NULL, points to a variable that will receive the
* number of characters we need to insert (and no formatting
* will actually occur).
* @append_zero_char: If non-zero, put a trailing zero at the end of
* of the resulting string, if and only if we modified the
* string.
*
* Reads thee current locale and calls _Py_InsertThousandsGrouping().
**/
int
_Py_InsertThousandsGroupingLocale(STRINGLIB_CHAR *buffer,
Py_ssize_t n_buffer,
Py_ssize_t n_digits,
Py_ssize_t buf_size,
Py_ssize_t *count,
int append_zero_char)
{
struct lconv *locale_data = localeconv();
const char *grouping = locale_data->grouping;
const char *thousands_sep = locale_data->thousands_sep;
return _Py_InsertThousandsGrouping(buffer, n_buffer, n_digits,
buf_size, count,
append_zero_char, grouping,
thousands_sep);
} }
#endif /* STRINGLIB_LOCALEUTIL_H */ #endif /* STRINGLIB_LOCALEUTIL_H */

View file

@ -24,5 +24,6 @@
#define STRINGLIB_CMP memcmp #define STRINGLIB_CMP memcmp
#define STRINGLIB_TOSTR PyObject_Str #define STRINGLIB_TOSTR PyObject_Str
#define STRINGLIB_GROUPING _PyBytes_InsertThousandsGrouping #define STRINGLIB_GROUPING _PyBytes_InsertThousandsGrouping
#define STRINGLIB_GROUPING_LOCALE _PyBytes_InsertThousandsGroupingLocale
#define STRINGLIB_TOASCII PyObject_Repr #define STRINGLIB_TOASCII PyObject_Repr
#endif /* !STRINGLIB_STRINGDEFS_H */ #endif /* !STRINGLIB_STRINGDEFS_H */

View file

@ -22,6 +22,7 @@
#define STRINGLIB_RESIZE PyUnicode_Resize #define STRINGLIB_RESIZE PyUnicode_Resize
#define STRINGLIB_CHECK PyUnicode_Check #define STRINGLIB_CHECK PyUnicode_Check
#define STRINGLIB_GROUPING _PyUnicode_InsertThousandsGrouping #define STRINGLIB_GROUPING _PyUnicode_InsertThousandsGrouping
#define STRINGLIB_GROUPING_LOCALE _PyUnicode_InsertThousandsGroupingLocale
#if PY_VERSION_HEX < 0x03000000 #if PY_VERSION_HEX < 0x03000000
#define STRINGLIB_TOSTR PyObject_Unicode #define STRINGLIB_TOSTR PyObject_Unicode

View file

@ -5635,6 +5635,7 @@ int PyUnicode_EncodeDecimal(Py_UNICODE *s,
#include "stringlib/partition.h" #include "stringlib/partition.h"
#define _Py_InsertThousandsGrouping _PyUnicode_InsertThousandsGrouping #define _Py_InsertThousandsGrouping _PyUnicode_InsertThousandsGrouping
#define _Py_InsertThousandsGroupingLocale _PyUnicode_InsertThousandsGroupingLocale
#include "stringlib/localeutil.h" #include "stringlib/localeutil.h"
/* helper macro to fixup start/end slice values */ /* helper macro to fixup start/end slice values */

View file

@ -368,7 +368,7 @@ add_thousands_grouping(char* buffer, size_t buf_size)
/* At this point, p points just past the right-most character we /* At this point, p points just past the right-most character we
want to format. We need to add the grouping string for the want to format. We need to add the grouping string for the
characters between buffer and p. */ characters between buffer and p. */
return _PyBytes_InsertThousandsGrouping(buffer, len, p-buffer, return _PyBytes_InsertThousandsGroupingLocale(buffer, len, p-buffer,
buf_size, NULL, 1); buf_size, NULL, 1);
} }