]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue 2264: empty float presentation type needs to have at least one digit past the...
authorEric Smith <eric@trueblade.com>
Mon, 17 Mar 2008 11:01:01 +0000 (11:01 +0000)
committerEric Smith <eric@trueblade.com>
Mon, 17 Mar 2008 11:01:01 +0000 (11:01 +0000)
Added "Z" format_char to PyOS_ascii_formatd to support empty float presentation type.
Renamed buf_size in PyOS_ascii_formatd to more accurately reflect it's meaning.
Modified format.__float__ to use the new "Z" format as the default.
Added test cases.

Lib/test/test_types.py
Objects/stringlib/formatter.h
Python/pystrtod.c

index bf218db3b5927c1b2b7f6cac0ad699f89059f794..4fa2c01926b8b5fd5b1ca28b1d9885f46b1e3b88 100644 (file)
@@ -521,6 +521,15 @@ class TypesTests(unittest.TestCase):
         test(1.1234e20, 'e', '1.123400e+20')
         test(1.1234e20, 'E', '1.123400E+20')
 
+        # No format code means use g, but must have a decimal
+        # and a number after the decimal.  This is tricky, because
+        # a totaly empty format specifier means something else.
+        # So, just use a sign flag
+        test(1e200, '+g', '+1e+200')
+        test(1e200, '+', '+1.0e+200')
+        test(1.1e200, '+g', '+1.1e+200')
+        test(1.1e200, '+', '+1.1e+200')
+
         # % formatting
         test(-1.0, '%', '-100.000000%')
 
index e8e83f4b798a074b2e627cce2e846516e758b76f..3ca14fac816436114d321ecf2df5b1ad42ef055a 100644 (file)
@@ -925,11 +925,16 @@ FORMAT_FLOAT(PyObject *value, PyObject *args)
     }
 
     /* parse the format_spec */
-    if (!parse_internal_render_format_spec(format_spec, &format, 'g'))
+    if (!parse_internal_render_format_spec(format_spec, &format, '\0'))
         goto done;
 
     /* type conversion? */
     switch (format.type) {
+    case '\0':
+       /* 'Z' means like 'g', but with at least one decimal.  See
+          PyOS_ascii_formatd */
+       format.type = 'Z';
+       /* Deliberate fall through to the next case statement */
     case 'e':
     case 'E':
     case 'f':
index 16efa9d3ab8c2987d56a0c6436316ab3ee121e09..e68c46086fa2d480314de774edac8c53ef619aed 100644 (file)
@@ -198,7 +198,7 @@ as necessary to represent the exponent.
 /**
  * PyOS_ascii_formatd:
  * @buffer: A buffer to place the resulting string in
- * @buf_len: The length of the buffer.
+ * @buf_size: The length of the buffer.
  * @format: The printf()-style format to use for the
  *          code to use for converting. 
  * @d: The #gdouble to convert
@@ -209,12 +209,14 @@ as necessary to represent the exponent.
  * specifiers are 'e', 'E', 'f', 'F', 'g', 'G', and 'n'.
  * 
  * 'n' is the same as 'g', except it uses the current locale.
+ * 'Z' is the same as 'g', except it always has a decimal and
+ *     at least one digit after the decimal.
  *
  * Return value: The pointer to the buffer with the converted string.
  **/
 char *
 PyOS_ascii_formatd(char       *buffer, 
-                  size_t      buf_len
+                  size_t      buf_size
                   const char *format, 
                   double      d)
 {
@@ -227,20 +229,13 @@ PyOS_ascii_formatd(char       *buffer,
           can't modify it directly.  FLOAT_FORMATBUFLEN should be longer than
           we ever need this to be.  There's an upcoming check to ensure it's
           big enough. */
+       /* Issue 2264: code 'Z' requires copying the format.  'Z' is 'g', but
+          also with at least one character past the decimal. */
        char tmp_format[FLOAT_FORMATBUFLEN];
 
-/*     g_return_val_if_fail (buffer != NULL, NULL); */
-/*     g_return_val_if_fail (format[0] == '%', NULL); */
-/*     g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL); */
-
        /* The last character in the format string must be the format char */
        format_char = format[format_len - 1];
 
-/*     g_return_val_if_fail (format_char == 'e' || format_char == 'E' || */
-/*                           format_char == 'f' || format_char == 'F' || */
-/*                           format_char == 'g' || format_char == 'G', */
-/*                           NULL); */
-
        if (format[0] != '%')
                return NULL;
 
@@ -251,15 +246,20 @@ PyOS_ascii_formatd(char       *buffer,
        if (strpbrk(format + 1, "'l%"))
                return NULL;
 
+       /* Also curious about this function is that it accepts format strings
+          like "%xg", which are invalid for floats.  In general, the
+          interface to this function is not very good, but changing it is
+          difficult because it's a public API. */
+
        if (!(format_char == 'e' || format_char == 'E' || 
              format_char == 'f' || format_char == 'F' || 
              format_char == 'g' || format_char == 'G' ||
-             format_char == 'n'))
+             format_char == 'n' || format_char == 'Z'))
                return NULL;
 
-       /* Map 'n' format_char to 'g', by copying the format string and
-          replacing the final 'n' with a 'g' */
-       if (format_char == 'n') {
+       /* Map 'n' or 'Z' format_char to 'g', by copying the format string and
+          replacing the final char with a 'g' */
+       if (format_char == 'n' || format_char == 'Z') {
                if (format_len + 1 >= sizeof(tmp_format)) {
                        /* The format won't fit in our copy.  Error out.  In
                           practice, this will never happen and will be detected
@@ -271,8 +271,9 @@ PyOS_ascii_formatd(char       *buffer,
                format = tmp_format;
        }
 
+
        /* Have PyOS_snprintf do the hard work */
-       PyOS_snprintf(buffer, buf_len, format, d);
+       PyOS_snprintf(buffer, buf_size, format, d);
 
        /* Get the current local, and find the decimal point character (or
           string?).  Convert that string back to a dot.  Do not do this if
@@ -360,7 +361,7 @@ PyOS_ascii_formatd(char       *buffer,
                           until there are 2, if there's enough room */
                        int zeros = MIN_EXPONENT_DIGITS - exponent_digit_cnt;
                        if (start + zeros + exponent_digit_cnt + 1
-                             < buffer + buf_len) {
+                             < buffer + buf_size) {
                                memmove(start + zeros, start,
                                        exponent_digit_cnt + 1);
                                memset(start, '0', zeros);
@@ -368,6 +369,49 @@ PyOS_ascii_formatd(char       *buffer,
                }
        }
 
+       /* If format_char is 'Z', make sure we have at least one character
+          after the decimal point (and make sure we have a decimal point). */
+       if (format_char == 'Z') {
+               int insert_count = 0;
+               char* chars_to_insert;
+
+               /* search for the first non-digit character */
+               p = buffer;
+               while (*p && isdigit(Py_CHARMASK(*p)))
+                       ++p;
+
+               if (*p == '.') {
+                       if (isdigit(Py_CHARMASK(*(p+1)))) {
+                               /* Nothing to do, we already have a decimal
+                                  point and a digit after it */
+                       }
+                       else {
+                               /* We have a decimal point, but no following
+                                  digit.  Insert a zero after the decimal. */
+                               ++p;
+                               chars_to_insert = "0";
+                               insert_count = 1;
+                       }
+               }
+               else {
+                       chars_to_insert = ".0";
+                       insert_count = 2;
+               }
+               if (insert_count) {
+                       size_t buf_len = strlen(buffer);
+                       if (buf_len + insert_count + 1 >= buf_size) {
+                               /* If there is not enough room in the buffer
+                                  for the additional text, just skip it.  It's
+                                  not worth generating an error over. */
+                       }
+                       else {
+                               memmove(p + insert_count, p,
+                                       buffer + strlen(buffer) - p + 1);
+                               memcpy(p, chars_to_insert, insert_count);
+                       }
+               }
+       }
+
        return buffer;
 }