[Python-checkins] r63078 - in python/trunk: Include/stringobject.h Lib/test/test_types.py Makefile.pre.in Objects/stringlib/formatter.h Objects/stringlib/localeutil.h Objects/stringlib/stringdefs.h Objects/stringlib/unicodedefs.h Objects/stringobject.c Python/pystrtod.c

eric.smith python-checkins at python.org
Sun May 11 21:52:49 CEST 2008


Author: eric.smith
Date: Sun May 11 21:52:48 2008
New Revision: 63078

Log:
Addresses issue 2802: 'n' formatting for integers.

Adds 'n' as a format specifier for integers, to mirror the same
specifier which is already available for floats.  'n' is the same as
'd', but inserts the current locale-specific thousands grouping.

I added this as a stringlib function, but it's only used by str type,
not unicode.  This is because of an implementation detail in
unicode.format(), which does its own str->unicode conversion.  But the
unicode version will be needed in 3.0, and it may be needed by other
code eventually in 2.6 (maybe decimal?), so I left it as a stringlib
implementation.  As long as the unicode version isn't instantiated,
there's no overhead for this.


Added:
   python/trunk/Objects/stringlib/localeutil.h
Modified:
   python/trunk/Include/stringobject.h
   python/trunk/Lib/test/test_types.py
   python/trunk/Makefile.pre.in
   python/trunk/Objects/stringlib/formatter.h
   python/trunk/Objects/stringlib/stringdefs.h
   python/trunk/Objects/stringlib/unicodedefs.h
   python/trunk/Objects/stringobject.c
   python/trunk/Python/pystrtod.c

Modified: python/trunk/Include/stringobject.h
==============================================================================
--- python/trunk/Include/stringobject.h	(original)
+++ python/trunk/Include/stringobject.h	Sun May 11 21:52:48 2008
@@ -176,7 +176,17 @@
 				   (only possible for 0-terminated
 				   strings) */
     );
-    
+
+/* Using the current locale, insert the thousands grouping
+   into the string pointed to by buffer.  For the argument descriptions,
+   see Objects/stringlib/localeutil.h */
+
+PyAPI_FUNC(int) _PyString_InsertThousandsGrouping(char *buffer,
+						  Py_ssize_t len,
+						  char *plast,
+						  Py_ssize_t buf_size,
+						  Py_ssize_t *count,
+						  int append_zero_char);
 
 #ifdef __cplusplus
 }

Modified: python/trunk/Lib/test/test_types.py
==============================================================================
--- python/trunk/Lib/test/test_types.py	(original)
+++ python/trunk/Lib/test/test_types.py	Sun May 11 21:52:48 2008
@@ -377,7 +377,7 @@
 
         # ensure that float type specifiers work; format converts
         #  the int to a float
-        for format_spec in 'eEfFgGn%':
+        for format_spec in 'eEfFgG%':
             for value in [0, 1, -1, 100, -100, 1234567890, -1234567890]:
                 self.assertEqual(value.__format__(format_spec),
                                  float(value).__format__(format_spec))
@@ -472,7 +472,7 @@
 
         # ensure that float type specifiers work; format converts
         #  the long to a float
-        for format_spec in 'eEfFgGn%':
+        for format_spec in 'eEfFgG%':
             for value in [0L, 1L, -1L, 100L, -100L, 1234567890L, -1234567890L]:
                 self.assertEqual(value.__format__(format_spec),
                                  float(value).__format__(format_spec))
@@ -486,6 +486,17 @@
             self.assertEqual(locale.format('%g', x, grouping=True), format(x, 'n'))
             self.assertEqual(locale.format('%.10g', x, grouping=True), format(x, '.10n'))
 
+    @run_with_locale('LC_NUMERIC', 'en_US.UTF8')
+    def test_int__format__locale(self):
+        # test locale support for __format__ code 'n' for integers
+
+        x = 123456789012345678901234567890
+        for i in range(0, 30):
+            self.assertEqual(locale.format('%d', x, grouping=True), format(x, 'n'))
+
+            # move to the next integer to test
+            x = x // 10
+
     def test_float__format__(self):
         # these should be rewritten to use both format(x, spec) and
         # x.__format__(spec)

Modified: python/trunk/Makefile.pre.in
==============================================================================
--- python/trunk/Makefile.pre.in	(original)
+++ python/trunk/Makefile.pre.in	Sun May 11 21:52:48 2008
@@ -549,7 +549,8 @@
 		$(srcdir)/Objects/stringlib/stringdefs.h \
 		$(srcdir)/Objects/stringlib/string_format.h \
 		$(srcdir)/Objects/stringlib/transmogrify.h \
-		$(srcdir)/Objects/stringlib/unicodedefs.h
+		$(srcdir)/Objects/stringlib/unicodedefs.h \
+		$(srcdir)/Objects/stringlib/localeutil.h
 
 Objects/unicodeobject.o: $(srcdir)/Objects/unicodeobject.c \
 				$(STRINGLIB_HEADERS)

Modified: python/trunk/Objects/stringlib/formatter.h
==============================================================================
--- python/trunk/Objects/stringlib/formatter.h	(original)
+++ python/trunk/Objects/stringlib/formatter.h	Sun May 11 21:52:48 2008
@@ -453,6 +453,9 @@
     Py_ssize_t n_digits;       /* count of digits need from the computed
                                   string */
     Py_ssize_t n_leading_chars;
+    Py_ssize_t n_grouping_chars = 0; /* Count of additional chars to
+					allocate, used for 'n'
+					formatting. */
     NumberFieldWidths spec;
     long x;
 
@@ -523,6 +526,7 @@
             break;
         default:  /* shouldn't be needed, but stops a compiler warning */
         case 'd':
+        case 'n':
             base = 10;
             leading_chars_to_skip = 0;
             break;
@@ -555,8 +559,15 @@
     /* Calculate the widths of the various leading and trailing parts */
     calc_number_widths(&spec, sign, n_digits, format);
 
+    if (format->type == 'n')
+	    /* Compute how many additional chars we need to allocate
+	       to hold the thousands grouping. */
+	    STRINGLIB_GROUPING(pnumeric_chars, n_digits,
+			       pnumeric_chars+n_digits,
+			       0, &n_grouping_chars, 0);
+
     /* Allocate a new string to hold the result */
-    result = STRINGLIB_NEW(NULL, spec.n_total);
+    result = STRINGLIB_NEW(NULL, spec.n_total + n_grouping_chars);
     if (!result)
 	goto done;
     p = STRINGLIB_STR(result);
@@ -567,13 +578,26 @@
 	    pnumeric_chars,
 	    n_digits * sizeof(STRINGLIB_CHAR));
 
-    /* if X, convert to uppercase */
+    /* If type is 'X', convert to uppercase */
     if (format->type == 'X') {
 	Py_ssize_t t;
 	for (t = 0; t < n_digits; ++t)
 	    p[t + n_leading_chars] = STRINGLIB_TOUPPER(p[t + n_leading_chars]);
     }
 
+    /* Insert the grouping, if any, after the uppercasing of 'X', so we can
+       ensure that grouping chars won't be affeted. */
+    if (n_grouping_chars && format->type == 'n') {
+	    /* We know this can't fail, since we've already
+	       reserved enough space. */
+	    STRINGLIB_CHAR *pstart = p + n_leading_chars;
+	    int r = STRINGLIB_GROUPING(pstart, n_digits,
+				       pstart + n_digits,
+				       spec.n_total+n_grouping_chars-n_leading_chars,
+				       NULL, 0);
+	    assert(r);
+    }
+
     /* Fill in the non-digit parts */
     fill_number(p, &spec, n_digits,
                 format->fill_char == '\0' ? ' ' : format->fill_char);
@@ -841,6 +865,7 @@
     case 'o':
     case 'x':
     case 'X':
+    case 'n':
         /* no type conversion needed, already an int (or long).  do
 	   the formatting */
 	    result = format_int_or_long_internal(value, &format, tostring);
@@ -852,7 +877,6 @@
     case 'F':
     case 'g':
     case 'G':
-    case 'n':
     case '%':
         /* convert to float */
         tmp = PyNumber_Float(value);

Added: python/trunk/Objects/stringlib/localeutil.h
==============================================================================
--- (empty file)
+++ python/trunk/Objects/stringlib/localeutil.h	Sun May 11 21:52:48 2008
@@ -0,0 +1,121 @@
+/* stringlib: locale related helpers implementation */
+
+#ifndef STRINGLIB_LOCALEUTIL_H
+#define STRINGLIB_LOCALEUTIL_H
+
+#include <locale.h>
+
+/**
+ * _Py_InsertThousandsGrouping:
+ * @buffer: A pointer to the start of a string.
+ * @len: The length of the string.
+ * @plast: A pointer to the end of of the digits in the string.  This
+ *         may be before the end of the string (if the string contains
+ *         decimals, for example).
+ * @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.
+ *
+ * Inserts thousand grouping characters (as defined in the current
+ *  locale) into the string between buffer and plast.  If count is
+ *  non-NULL, don't do any formatting, just count the number of
+ *  characters to insert.  This is used by the caller to appropriately
+ *  resize the buffer, if needed.
+ *
+ * Return value: 0 on error, else 1.  Note that no error can occur if
+ *  count is non-NULL.
+ *
+ * This name won't be used, the includer of this file should define
+ *  it to be the actual function name, based on unicode or string.
+ **/
+int
+_Py_InsertThousandsGrouping(STRINGLIB_CHAR *buffer,
+			    Py_ssize_t len,
+			    STRINGLIB_CHAR *plast,
+			    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;
+	Py_ssize_t thousands_sep_len = strlen(thousands_sep);
+	STRINGLIB_CHAR *pend = buffer + len; /* current end of buffer */
+	STRINGLIB_CHAR *pmax = buffer + buf_size;       /* max of buffer */
+	char current_grouping;
+
+	/* Initialize the character count, if we're just counting. */
+	if (count)
+		*count = 0;
+
+	/* Starting at plast and working right-to-left, keep track of
+	   what grouping needs to be added and insert that. */
+	current_grouping = *grouping++;
+
+	/* If the first character is 0, perform no grouping at all. */
+	if (current_grouping == 0)
+		return 1;
+
+	while (plast - buffer > current_grouping) {
+		/* Always leave buffer and pend valid at the end of this
+		   loop, since we might leave with a return statement. */
+
+		plast -= current_grouping;
+		if (count) {
+			/* We're only counting, not touching the memory. */
+			*count += thousands_sep_len;
+		}
+		else {
+			/* Do the formatting. */
+
+			/* Is there room to insert thousands_sep_len chars? */
+			if (pmax - pend < thousands_sep_len)
+				/* No room. */
+				return 0;
+
+			/* Move the rest of the string down. */
+			memmove(plast + thousands_sep_len,
+				plast,
+				(pend - plast) * sizeof(STRINGLIB_CHAR));
+			/* Copy the thousands_sep chars into the buffer. */
+#if STRINGLIB_IS_UNICODE
+			/* Convert from the char's of the thousands_sep from
+			   the locale into unicode. */
+			{
+				Py_ssize_t i;
+				for (i = 0; i < thousands_sep_len; ++i)
+					plast[i] = thousands_sep[i];
+			}
+#else
+			/* No conversion, just memcpy the thousands_sep. */
+			memcpy(plast, thousands_sep, thousands_sep_len);
+#endif
+		}
+
+		/* Adjust end pointer. */
+		pend += thousands_sep_len;
+
+		/* Move to the next grouping character, unless we're
+		   repeating (which is designated by a grouping of 0). */
+		if (*grouping != 0) {
+			current_grouping = *grouping++;
+			if (current_grouping == CHAR_MAX)
+				/* We're done. */
+				break;
+		}
+	}
+	if (append_zero_char) {
+		/* Append a zero character to mark the end of the string,
+		   if there's room. */
+		if (pend - plast < 1)
+			/* No room, error. */
+			return 0;
+		*pend = 0;
+	}
+	return 1;
+}
+#endif /* STRINGLIB_LOCALEUTIL_H */

Modified: python/trunk/Objects/stringlib/stringdefs.h
==============================================================================
--- python/trunk/Objects/stringlib/stringdefs.h	(original)
+++ python/trunk/Objects/stringlib/stringdefs.h	Sun May 11 21:52:48 2008
@@ -23,5 +23,6 @@
 #define STRINGLIB_CHECK          PyString_Check
 #define STRINGLIB_CMP            memcmp
 #define STRINGLIB_TOSTR          PyObject_Str
+#define STRINGLIB_GROUPING       _PyString_InsertThousandsGrouping
 
 #endif /* !STRINGLIB_STRINGDEFS_H */

Modified: python/trunk/Objects/stringlib/unicodedefs.h
==============================================================================
--- python/trunk/Objects/stringlib/unicodedefs.h	(original)
+++ python/trunk/Objects/stringlib/unicodedefs.h	Sun May 11 21:52:48 2008
@@ -21,6 +21,7 @@
 #define STRINGLIB_NEW            PyUnicode_FromUnicode
 #define STRINGLIB_RESIZE         PyUnicode_Resize
 #define STRINGLIB_CHECK          PyUnicode_Check
+#define STRINGLIB_GROUPING       _PyUnicode_InsertThousandsGrouping
 
 #if PY_VERSION_HEX < 0x03000000
 #define STRINGLIB_TOSTR          PyObject_Unicode

Modified: python/trunk/Objects/stringobject.c
==============================================================================
--- python/trunk/Objects/stringobject.c	(original)
+++ python/trunk/Objects/stringobject.c	Sun May 11 21:52:48 2008
@@ -784,6 +784,10 @@
 #include "stringlib/find.h"
 #include "stringlib/partition.h"
 
+#define _Py_InsertThousandsGrouping _PyString_InsertThousandsGrouping
+#include "stringlib/localeutil.h"
+
+
 
 static int
 string_print(PyStringObject *op, FILE *fp, int flags)

Modified: python/trunk/Python/pystrtod.c
==============================================================================
--- python/trunk/Python/pystrtod.c	(original)
+++ python/trunk/Python/pystrtod.c	Sun May 11 21:52:48 2008
@@ -343,14 +343,9 @@
 Py_LOCAL_INLINE(int)
 add_thousands_grouping(char* buffer, size_t buf_size)
 {
+	Py_ssize_t len = strlen(buffer);
 	struct lconv *locale_data = localeconv();
-	const char *grouping = locale_data->grouping;
-	const char *thousands_sep = locale_data->thousands_sep;
-	size_t thousands_sep_len = strlen(thousands_sep);
 	const char *decimal_point = locale_data->decimal_point;
-	char *pend = buffer + strlen(buffer); /* current end of buffer */
-	char *pmax = buffer + buf_size;       /* max of buffer */
-	char current_grouping;
 
 	/* Find the decimal point, if any.  We're only concerned
 	   about the characters to the left of the decimal when
@@ -364,49 +359,13 @@
 		if (!p)
 			/* No exponent and no decimal.  Use the entire
 			   string. */
-			p = pend;
+			p = buffer + len;
 	}
 	/* At this point, p points just past the right-most character we
 	   want to format.  We need to add the grouping string for the
 	   characters between buffer and p. */
-
-	/* Starting at p and working right-to-left, keep track of
-	   what grouping needs to be added and insert that. */
-	current_grouping = *grouping++;
-
-	/* If the first character is 0, perform no grouping at all. */
-	if (current_grouping == 0)
-		return 1;
-
-	while (p - buffer > current_grouping) {
-		/* Always leave buffer and pend valid at the end of this
-		   loop, since we might leave with a return statement. */
-
-		/* Is there room to insert thousands_sep_len chars?. */
-		if (pmax - pend <= thousands_sep_len)
-			/* No room. */
-			return 0;
-
-		/* Move the rest of the string down. */
-		p -= current_grouping;
-		memmove(p + thousands_sep_len,
-			p,
-			pend - p + 1);
-		/* Adjust end pointer. */
-		pend += thousands_sep_len;
-		/* Copy the thousands_sep chars into the buffer. */
-		memcpy(p, thousands_sep, thousands_sep_len);
-
-		/* Move to the next grouping character, unless we're
-		   repeating (which is designated by a grouping of 0). */
-		if (*grouping != 0) {
-			current_grouping = *grouping++;
-			if (current_grouping == CHAR_MAX)
-				/* We're done. */
-				return 1;
-		}
-	}
-	return 1;
+	return _PyString_InsertThousandsGrouping(buffer, len, p,
+						 buf_size, NULL, 1);
 }
 
 /* see FORMATBUFLEN in unicodeobject.c */


More information about the Python-checkins mailing list