Skip to content

Commit a3b1ac8

Browse files
committed
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).
1 parent f8c8b6d commit a3b1ac8

File tree

10 files changed

+190
-88
lines changed

10 files changed

+190
-88
lines changed

Include/bytesobject.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,25 @@ PyAPI_FUNC(int) PyBytes_AsStringAndSize(
9191
into the string pointed to by buffer. For the argument descriptions,
9292
see Objects/stringlib/localeutil.h */
9393

94-
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer,
94+
PyAPI_FUNC(int) _PyBytes_InsertThousandsGroupingLocale(char *buffer,
9595
Py_ssize_t n_buffer,
9696
Py_ssize_t n_digits,
9797
Py_ssize_t buf_size,
9898
Py_ssize_t *count,
9999
int append_zero_char);
100100

101+
/* Using explicit passed-in values, insert the thousands grouping
102+
into the string pointed to by buffer. For the argument descriptions,
103+
see Objects/stringlib/localeutil.h */
104+
PyAPI_FUNC(int) _PyBytes_InsertThousandsGrouping(char *buffer,
105+
Py_ssize_t n_buffer,
106+
Py_ssize_t n_digits,
107+
Py_ssize_t buf_size,
108+
Py_ssize_t *count,
109+
int append_zero_char,
110+
const char *grouping,
111+
const char *thousands_sep);
112+
101113
/* Flags used by string formatting */
102114
#define F_LJUST (1<<0)
103115
#define F_SIGN (1<<1)

Include/unicodeobject.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1482,13 +1482,24 @@ PyAPI_FUNC(PyObject *) _PyUnicode_XStrip(
14821482
into the string pointed to by buffer. For the argument descriptions,
14831483
see Objects/stringlib/localeutil.h */
14841484

1485-
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer,
1485+
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGroupingLocale(Py_UNICODE *buffer,
14861486
Py_ssize_t n_buffer,
14871487
Py_ssize_t n_digits,
14881488
Py_ssize_t buf_size,
14891489
Py_ssize_t *count,
14901490
int append_zero_char);
14911491

1492+
/* Using explicit passed-in values, insert the thousands grouping
1493+
into the string pointed to by buffer. For the argument descriptions,
1494+
see Objects/stringlib/localeutil.h */
1495+
PyAPI_FUNC(int) _PyUnicode_InsertThousandsGrouping(Py_UNICODE *buffer,
1496+
Py_ssize_t n_buffer,
1497+
Py_ssize_t n_digits,
1498+
Py_ssize_t buf_size,
1499+
Py_ssize_t *count,
1500+
int append_zero_char,
1501+
const char *grouping,
1502+
const char *thousands_sep);
14921503
/* === Characters Type APIs =============================================== */
14931504

14941505
/* Helper array used by Py_UNICODE_ISSPACE(). */

Lib/test/test_types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,15 @@ def test(i, format_spec, result):
338338
test(123456, "#012X", '0X000001E240')
339339
test(-123456, "#012X", '-0X00001E240')
340340

341+
test(123, ',', '123')
342+
test(-123, ',', '-123')
343+
test(1234, ',', '1,234')
344+
test(-1234, ',', '-1,234')
345+
test(123456, ',', '123,456')
346+
test(-123456, ',', '-123,456')
347+
test(1234567, ',', '1,234,567')
348+
test(-1234567, ',', '-1,234,567')
349+
341350
# make sure these are errors
342351

343352
# precision disallowed
@@ -347,6 +356,8 @@ def test(i, format_spec, result):
347356
# format spec must be string
348357
self.assertRaises(TypeError, 3 .__format__, None)
349358
self.assertRaises(TypeError, 3 .__format__, 0)
359+
# can't have ',' with 'n'
360+
self.assertRaises(ValueError, 3 .__format__, ",n")
350361

351362
# ensure that only int and float type specifiers work
352363
for format_spec in ([chr(x) for x in range(ord('a'), ord('z')+1)] +

Objects/bytesobject.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ PyBytes_AsStringAndSize(register PyObject *obj,
583583
#include "stringlib/transmogrify.h"
584584

585585
#define _Py_InsertThousandsGrouping _PyBytes_InsertThousandsGrouping
586+
#define _Py_InsertThousandsGroupingLocale _PyBytes_InsertThousandsGroupingLocale
586587
#include "stringlib/localeutil.h"
587588

588589
PyObject *

Objects/stringlib/formatter.h

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ typedef struct {
120120
int alternate;
121121
STRINGLIB_CHAR sign;
122122
Py_ssize_t width;
123+
int thousands_separators;
123124
Py_ssize_t precision;
124125
STRINGLIB_CHAR type;
125126
} InternalFormatSpec;
@@ -149,6 +150,7 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
149150
format->alternate = 0;
150151
format->sign = '\0';
151152
format->width = -1;
153+
format->thousands_separators = 0;
152154
format->precision = -1;
153155
format->type = default_type;
154156

@@ -201,6 +203,12 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
201203
format->width = -1;
202204
}
203205

206+
/* Comma signifies add thousands separators */
207+
if (end-ptr && ptr[0] == ',') {
208+
format->thousands_separators = 1;
209+
++ptr;
210+
}
211+
204212
/* Parse field precision */
205213
if (end-ptr && ptr[0] == '.') {
206214
++ptr;
@@ -230,6 +238,11 @@ parse_internal_render_format_spec(STRINGLIB_CHAR *format_spec,
230238
++ptr;
231239
}
232240

241+
if (format->type == 'n' && format->thousands_separators) {
242+
PyErr_Format(PyExc_ValueError, "Cannot specify ',' with 'n'.");
243+
return 0;
244+
}
245+
233246
return 1;
234247
}
235248

@@ -630,8 +643,13 @@ format_int_or_long_internal(PyObject *value, const InternalFormatSpec *format,
630643
if (format->type == 'n')
631644
/* Compute how many additional chars we need to allocate
632645
to hold the thousands grouping. */
633-
STRINGLIB_GROUPING(NULL, n_digits, n_digits,
646+
STRINGLIB_GROUPING_LOCALE(NULL, n_digits, n_digits,
634647
0, &n_grouping_chars, 0);
648+
if (format->thousands_separators)
649+
/* Compute how many additional chars we need to allocate
650+
to hold the thousands grouping. */
651+
STRINGLIB_GROUPING(NULL, n_digits, n_digits,
652+
0, &n_grouping_chars, 0, "\3", ",");
635653

636654
/* Calculate the widths of the various leading and trailing parts */
637655
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,
670688
reserved enough space. */
671689
STRINGLIB_CHAR *pstart = p + n_leading_chars;
672690
#ifndef NDEBUG
673-
int r =
691+
int r;
692+
#endif
693+
if (format->type == 'n')
694+
#ifndef NDEBUG
695+
r =
674696
#endif
675-
STRINGLIB_GROUPING(pstart, n_digits, n_digits,
697+
STRINGLIB_GROUPING_LOCALE(pstart, n_digits, n_digits,
676698
spec.n_total+n_grouping_chars-n_leading_chars,
677699
NULL, 0);
700+
else
701+
#ifndef NDEBUG
702+
r =
703+
STRINGLIB_GROUPING(pstart, n_digits, n_digits,
704+
spec.n_total+n_grouping_chars-n_leading_chars,
705+
NULL, 0, "\3", ",");
706+
#endif
678707
assert(r);
679708
}
680709

Objects/stringlib/localeutil.h

Lines changed: 117 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
* @append_zero_char: If non-zero, put a trailing zero at the end of
1919
* of the resulting string, if and only if we modified the
2020
* string.
21+
* @grouping: see definition in localeconv().
22+
* @thousands_sep: see definition in localeconv().
2123
*
22-
* Inserts thousand grouping characters (as defined in the current
23-
* locale) into the string between buffer and buffer+n_digits. If
24-
* count is non-NULL, don't do any formatting, just count the number
25-
* of characters to insert. This is used by the caller to
24+
* Inserts thousand grouping characters (as defined by grouping and
25+
* thousands_sep) into the string between buffer and buffer+n_digits.
26+
* If count is non-NULL, don't do any formatting, just count the
27+
* number of characters to insert. This is used by the caller to
2628
* appropriately resize the buffer, if needed. If count is non-NULL,
2729
* buffer can be NULL (it is not dereferenced at all in that case).
2830
*
@@ -34,97 +36,130 @@
3436
**/
3537
int
3638
_Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer,
37-
Py_ssize_t n_buffer,
38-
Py_ssize_t n_digits,
39-
Py_ssize_t buf_size,
40-
Py_ssize_t *count,
41-
int append_zero_char)
39+
Py_ssize_t n_buffer,
40+
Py_ssize_t n_digits,
41+
Py_ssize_t buf_size,
42+
Py_ssize_t *count,
43+
int append_zero_char,
44+
const char *grouping,
45+
const char *thousands_sep)
4246
{
43-
struct lconv *locale_data = localeconv();
44-
const char *grouping = locale_data->grouping;
45-
const char *thousands_sep = locale_data->thousands_sep;
46-
Py_ssize_t thousands_sep_len = strlen(thousands_sep);
47-
STRINGLIB_CHAR *pend = NULL; /* current end of buffer */
48-
STRINGLIB_CHAR *pmax = NULL; /* max of buffer */
49-
char current_grouping;
50-
Py_ssize_t remaining = n_digits; /* Number of chars remaining to
51-
be looked at */
47+
Py_ssize_t thousands_sep_len = strlen(thousands_sep);
48+
STRINGLIB_CHAR *pend = NULL; /* current end of buffer */
49+
STRINGLIB_CHAR *pmax = NULL; /* max of buffer */
50+
char current_grouping;
51+
Py_ssize_t remaining = n_digits; /* Number of chars remaining to
52+
be looked at */
5253

53-
/* Initialize the character count, if we're just counting. */
54-
if (count)
55-
*count = 0;
56-
else {
57-
/* We're not just counting, we're modifying buffer */
58-
pend = buffer + n_buffer;
59-
pmax = buffer + buf_size;
60-
}
54+
/* Initialize the character count, if we're just counting. */
55+
if (count)
56+
*count = 0;
57+
else {
58+
/* We're not just counting, we're modifying buffer */
59+
pend = buffer + n_buffer;
60+
pmax = buffer + buf_size;
61+
}
6162

62-
/* Starting at the end and working right-to-left, keep track of
63-
what grouping needs to be added and insert that. */
64-
current_grouping = *grouping++;
63+
/* Starting at the end and working right-to-left, keep track of
64+
what grouping needs to be added and insert that. */
65+
current_grouping = *grouping++;
6566

66-
/* If the first character is 0, perform no grouping at all. */
67-
if (current_grouping == 0)
68-
return 1;
67+
/* If the first character is 0, perform no grouping at all. */
68+
if (current_grouping == 0)
69+
return 1;
6970

70-
while (remaining > current_grouping) {
71-
/* Always leave buffer and pend valid at the end of this
72-
loop, since we might leave with a return statement. */
71+
while (remaining > current_grouping) {
72+
/* Always leave buffer and pend valid at the end of this
73+
loop, since we might leave with a return statement. */
7374

74-
remaining -= current_grouping;
75-
if (count) {
76-
/* We're only counting, not touching the memory. */
77-
*count += thousands_sep_len;
78-
}
79-
else {
80-
/* Do the formatting. */
75+
remaining -= current_grouping;
76+
if (count) {
77+
/* We're only counting, not touching the memory. */
78+
*count += thousands_sep_len;
79+
}
80+
else {
81+
/* Do the formatting. */
8182

82-
STRINGLIB_CHAR *plast = buffer + remaining;
83+
STRINGLIB_CHAR *plast = buffer + remaining;
8384

84-
/* Is there room to insert thousands_sep_len chars? */
85-
if (pmax - pend < thousands_sep_len)
86-
/* No room. */
87-
return 0;
85+
/* Is there room to insert thousands_sep_len chars? */
86+
if (pmax - pend < thousands_sep_len)
87+
/* No room. */
88+
return 0;
8889

89-
/* Move the rest of the string down. */
90-
memmove(plast + thousands_sep_len,
91-
plast,
92-
(pend - plast) * sizeof(STRINGLIB_CHAR));
93-
/* Copy the thousands_sep chars into the buffer. */
90+
/* Move the rest of the string down. */
91+
memmove(plast + thousands_sep_len,
92+
plast,
93+
(pend - plast) * sizeof(STRINGLIB_CHAR));
94+
/* Copy the thousands_sep chars into the buffer. */
9495
#if STRINGLIB_IS_UNICODE
95-
/* Convert from the char's of the thousands_sep from
96-
the locale into unicode. */
97-
{
98-
Py_ssize_t i;
99-
for (i = 0; i < thousands_sep_len; ++i)
100-
plast[i] = thousands_sep[i];
101-
}
96+
/* Convert from the char's of the thousands_sep from
97+
the locale into unicode. */
98+
{
99+
Py_ssize_t i;
100+
for (i = 0; i < thousands_sep_len; ++i)
101+
plast[i] = thousands_sep[i];
102+
}
102103
#else
103-
/* No conversion, just memcpy the thousands_sep. */
104-
memcpy(plast, thousands_sep, thousands_sep_len);
104+
/* No conversion, just memcpy the thousands_sep. */
105+
memcpy(plast, thousands_sep, thousands_sep_len);
105106
#endif
106-
}
107+
}
107108

108-
/* Adjust end pointer. */
109-
pend += thousands_sep_len;
109+
/* Adjust end pointer. */
110+
pend += thousands_sep_len;
110111

111-
/* Move to the next grouping character, unless we're
112-
repeating (which is designated by a grouping of 0). */
113-
if (*grouping != 0) {
114-
current_grouping = *grouping++;
115-
if (current_grouping == CHAR_MAX)
116-
/* We're done. */
117-
break;
118-
}
119-
}
120-
if (append_zero_char) {
121-
/* Append a zero character to mark the end of the string,
122-
if there's room. */
123-
if (pend - (buffer + remaining) < 1)
124-
/* No room, error. */
125-
return 0;
126-
*pend = 0;
127-
}
128-
return 1;
112+
/* Move to the next grouping character, unless we're
113+
repeating (which is designated by a grouping of 0). */
114+
if (*grouping != 0) {
115+
current_grouping = *grouping++;
116+
if (current_grouping == CHAR_MAX)
117+
/* We're done. */
118+
break;
119+
}
120+
}
121+
if (append_zero_char) {
122+
/* Append a zero character to mark the end of the string,
123+
if there's room. */
124+
if (pend - (buffer + remaining) < 1)
125+
/* No room, error. */
126+
return 0;
127+
*pend = 0;
128+
}
129+
return 1;
130+
}
131+
132+
/**
133+
* _Py_InsertThousandsGroupingLocale:
134+
* @buffer: A pointer to the start of a string.
135+
* @n_buffer: The length of the string.
136+
* @n_digits: The number of digits in the string, in which we want
137+
* to put the grouping chars.
138+
* @buf_size: The maximum size of the buffer pointed to by buffer.
139+
* @count: If non-NULL, points to a variable that will receive the
140+
* number of characters we need to insert (and no formatting
141+
* will actually occur).
142+
* @append_zero_char: If non-zero, put a trailing zero at the end of
143+
* of the resulting string, if and only if we modified the
144+
* string.
145+
*
146+
* Reads thee current locale and calls _Py_InsertThousandsGrouping().
147+
**/
148+
int
149+
_Py_InsertThousandsGroupingLocale(STRINGLIB_CHAR *buffer,
150+
Py_ssize_t n_buffer,
151+
Py_ssize_t n_digits,
152+
Py_ssize_t buf_size,
153+
Py_ssize_t *count,
154+
int append_zero_char)
155+
{
156+
struct lconv *locale_data = localeconv();
157+
const char *grouping = locale_data->grouping;
158+
const char *thousands_sep = locale_data->thousands_sep;
159+
160+
return _Py_InsertThousandsGrouping(buffer, n_buffer, n_digits,
161+
buf_size, count,
162+
append_zero_char, grouping,
163+
thousands_sep);
129164
}
130165
#endif /* STRINGLIB_LOCALEUTIL_H */

0 commit comments

Comments
 (0)