From 0160cc564b9be6e396f04cd077e230a5233226eb Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 8 Mar 2018 20:55:55 -0800 Subject: [PATCH] Avoid losing info when formatting integers * 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 | 5 ++- etc/NEWS | 7 +++ src/editfns.c | 96 +++++++++++++++++----------------------- 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/doc/lispref/numbers.texi b/doc/lispref/numbers.texi index e692ee1cc2f..f1180cf754b 100644 --- a/doc/lispref/numbers.texi +++ b/doc/lispref/numbers.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index e89178955d3..3ef766b74b4 100644 --- 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). + * 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. diff --git a/src/editfns.c b/src/editfns.c index 96bb271b2d6..3a34dd0980b 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -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; -- 2.39.2