]> git.eshelyaron.com Git - emacs.git/commitdiff
Avoid losing info when formatting integers
authorPaul Eggert <eggert@cs.ucla.edu>
Fri, 9 Mar 2018 04:55:55 +0000 (20:55 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Fri, 9 Mar 2018 04:57:01 +0000 (20:57 -0800)
* doc/lispref/numbers.texi (Integer Basics): Clarify that
out-of-range integers are treated as floating point only when the
integers are decimal.
* etc/NEWS: Mention changes.
* src/editfns.c (styled_format): Use %.0f when formatting %d or %i
values outside machine integer range, to avoid losing info.
Signal an error for %o or %x values that are too large to be
formatted, to avoid losing info.

doc/lispref/numbers.texi
etc/NEWS
src/editfns.c

index e692ee1cc2f2bb5ec12c0c2c56a9965ab7e50d9e..f1180cf754bb9cf170f4a93a7cb19ea0fb0125e0 100644 (file)
@@ -53,8 +53,9 @@ but many machines provide a wider range.  Many examples in this
 chapter assume the minimum integer width of 30 bits.
 @cindex overflow
 
-  The Lisp reader reads an integer as a sequence of digits with optional
-initial sign and optional final period.  An integer that is out of the
+  The Lisp reader reads an integer as a nonempty sequence
+of decimal digits with optional initial sign and optional
+final period.  A decimal integer that is out of the
 Emacs range is treated as a floating-point number.
 
 @example
index 07f6d04a740b01f4827289c091354b107a80afa2..14926ba2e3b90309d3848b747fd52e3e388a360a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -302,6 +302,10 @@ as new-style, bind the new variable 'force-new-style-backquotes' to t.
 'cl-struct-define' whose name clashes with a builtin type (e.g.,
 'integer' or 'hash-table') now signals an error.
 
+** When formatting a floating-point number as an octal or hexadecimal
+integer, Emacs now signals an error if the number is too large for the
+implementation to format (Bug#30408).
+
 \f
 * Lisp Changes in Emacs 27.1
 
@@ -343,6 +347,9 @@ remote systems, which support this check.
 If the optional third argument is non-nil, 'make-string' will produce
 a multibyte string even if its second argument is an ASCII character.
 
+** (format "%d" X) no longer mishandles a floating-point number X that
+does not fit in a machine integer (Bug#30408).
+
 ** New JSON parsing and serialization functions 'json-serialize',
 'json-insert', 'json-parse-string', and 'json-parse-buffer'.  These
 are implemented in C using the Jansson library.
index 96bb271b2d6f6bb8ed38cfa3dda326dbb5467050..3a34dd0980b77f6055a255478eace841ebd78a4b 100644 (file)
@@ -4563,32 +4563,30 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
                 and with pM inserted for integer formats.
                 At most two flags F can be specified at once.  */
              char convspec[sizeof "%FF.*d" + max (INT_AS_LDBL, pMlen)];
-             {
-               char *f = convspec;
-               *f++ = '%';
-               /* MINUS_FLAG and ZERO_FLAG are dealt with later.  */
-               *f = '+'; f +=  plus_flag;
-               *f = ' '; f += space_flag;
-               *f = '#'; f += sharp_flag;
-                *f++ = '.';
-                *f++ = '*';
-               if (float_conversion)
-                 {
-                   if (INT_AS_LDBL)
-                     {
-                       *f = 'L';
-                       f += INTEGERP (arg);
-                     }
-                 }
-               else if (conversion != 'c')
-                 {
-                   memcpy (f, pMd, pMlen);
-                   f += pMlen;
-                   zero_flag &= ! precision_given;
-                 }
-               *f++ = conversion;
-               *f = '\0';
-             }
+             char *f = convspec;
+             *f++ = '%';
+             /* MINUS_FLAG and ZERO_FLAG are dealt with later.  */
+             *f = '+'; f +=  plus_flag;
+             *f = ' '; f += space_flag;
+             *f = '#'; f += sharp_flag;
+             *f++ = '.';
+             *f++ = '*';
+             if (float_conversion)
+               {
+                 if (INT_AS_LDBL)
+                   {
+                     *f = 'L';
+                     f += INTEGERP (arg);
+                   }
+               }
+             else if (conversion != 'c')
+               {
+                 memcpy (f, pMd, pMlen);
+                 f += pMlen;
+                 zero_flag &= ! precision_given;
+               }
+             *f++ = conversion;
+             *f = '\0';
 
              int prec = -1;
              if (precision_given)
@@ -4630,29 +4628,20 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
                }
              else if (conversion == 'd' || conversion == 'i')
                {
-                 /* For float, maybe we should use "%1.0f"
-                    instead so it also works for values outside
-                    the integer range.  */
-                 printmax_t x;
                  if (INTEGERP (arg))
-                   x = XINT (arg);
+                   {
+                     printmax_t x = XINT (arg);
+                     sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x);
+                   }
                  else
                    {
-                     double d = XFLOAT_DATA (arg);
-                     if (d < 0)
-                       {
-                         x = TYPE_MINIMUM (printmax_t);
-                         if (x < d)
-                           x = d;
-                       }
-                     else
-                       {
-                         x = TYPE_MAXIMUM (printmax_t);
-                         if (d < x)
-                           x = d;
-                       }
+                     strcpy (f - pMlen - 1, "f");
+                     double x = XFLOAT_DATA (arg);
+                     sprintf_bytes = sprintf (sprintf_buf, convspec, 0, x);
+                     char c0 = sprintf_buf[0];
+                     bool signedp = ! ('0' <= c0 && c0 <= '9');
+                     prec = min (precision, sprintf_bytes - signedp);
                    }
-                 sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x);
                }
              else
                {
@@ -4663,22 +4652,19 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
                  else
                    {
                      double d = XFLOAT_DATA (arg);
-                     if (d < 0)
-                       x = 0;
-                     else
-                       {
-                         x = TYPE_MAXIMUM (uprintmax_t);
-                         if (d < x)
-                           x = d;
-                       }
+                     double uprintmax = TYPE_MAXIMUM (uprintmax_t);
+                     if (! (0 <= d && d < uprintmax + 1))
+                       xsignal1 (Qoverflow_error, arg);
+                     x = d;
                    }
                  sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x);
                }
 
              /* Now the length of the formatted item is known, except it omits
                 padding and excess precision.  Deal with excess precision
-                first.  This happens only when the format specifies
-                ridiculously large precision.  */
+                first.  This happens when the format specifies ridiculously
+                large precision, or when %d or %i formats a float that would
+                ordinarily need fewer digits than a specified precision.  */
              ptrdiff_t excess_precision
                = precision_given ? precision - prec : 0;
              ptrdiff_t leading_zeros = 0, trailing_zeros = 0;