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.
This commit is contained in:
Paul Eggert 2018-03-08 20:55:55 -08:00
parent cb0f634895
commit 80e145fc96
3 changed files with 51 additions and 57 deletions

View 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

View file

@ -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.

View 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;