mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-04-26 16:20:49 -07:00
Fewer rounding errors with (format "%f" fixnum)
* etc/NEWS: Document this. * src/editfns.c (styled_format): When formatting integers via a floating-point format, use long double instead of double conversion, if long double’s extra precision might help.
This commit is contained in:
parent
207de33030
commit
44e7ee2e35
2 changed files with 51 additions and 13 deletions
8
etc/NEWS
8
etc/NEWS
|
|
@ -907,6 +907,14 @@ compares their numerical values. According to this predicate,
|
|||
due to internal rounding errors. For example, (< most-positive-fixnum
|
||||
(+ 1.0 most-positive-fixnum)) now correctly returns t on 64-bit hosts.
|
||||
|
||||
---
|
||||
** On hosts like GNU/Linux x86-64 where a 'long double' fraction
|
||||
contains at least EMACS_INT_WIDTH - 3 bits, 'format' no longer returns
|
||||
incorrect answers due to internal rounding errors when formatting
|
||||
Emacs integers with %e, %f, or %g conversions. For example, on these
|
||||
hosts (eql N (string-to-number (format "%.0f" N))) now returns t for
|
||||
all Emacs integers N.
|
||||
|
||||
+++
|
||||
** The new function 'char-from-name' converts a Unicode name string
|
||||
to the corresponding character code.
|
||||
|
|
|
|||
|
|
@ -4145,6 +4145,9 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
}
|
||||
}
|
||||
|
||||
bool float_conversion
|
||||
= conversion == 'e' || conversion == 'f' || conversion == 'g';
|
||||
|
||||
if (conversion == 's')
|
||||
{
|
||||
/* handle case (precision[n] >= 0) */
|
||||
|
|
@ -4229,8 +4232,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
}
|
||||
}
|
||||
else if (! (conversion == 'c' || conversion == 'd'
|
||||
|| conversion == 'e' || conversion == 'f'
|
||||
|| conversion == 'g' || conversion == 'i'
|
||||
|| float_conversion || conversion == 'i'
|
||||
|| conversion == 'o' || conversion == 'x'
|
||||
|| conversion == 'X'))
|
||||
error ("Invalid format operation %%%c",
|
||||
|
|
@ -4242,11 +4244,22 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
{
|
||||
enum
|
||||
{
|
||||
/* Lower bound on the number of bits per
|
||||
base-FLT_RADIX digit. */
|
||||
DIG_BITS_LBOUND = FLT_RADIX < 16 ? 1 : 4,
|
||||
|
||||
/* 1 if integers should be formatted as long doubles,
|
||||
because they may be so large that there is a rounding
|
||||
error when converting them to double, and long doubles
|
||||
are wider than doubles. */
|
||||
INT_AS_LDBL = (DIG_BITS_LBOUND * DBL_MANT_DIG < FIXNUM_BITS - 1
|
||||
&& DBL_MANT_DIG < LDBL_MANT_DIG),
|
||||
|
||||
/* Maximum precision for a %f conversion such that the
|
||||
trailing output digit might be nonzero. Any precision
|
||||
larger than this will not yield useful information. */
|
||||
USEFUL_PRECISION_MAX =
|
||||
((1 - DBL_MIN_EXP)
|
||||
((1 - LDBL_MIN_EXP)
|
||||
* (FLT_RADIX == 2 || FLT_RADIX == 10 ? 1
|
||||
: FLT_RADIX == 16 ? 4
|
||||
: -1)),
|
||||
|
|
@ -4255,7 +4268,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
precision is no more than USEFUL_PRECISION_MAX.
|
||||
On all practical hosts, %f is the worst case. */
|
||||
SPRINTF_BUFSIZE =
|
||||
sizeof "-." + (DBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX,
|
||||
sizeof "-." + (LDBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX,
|
||||
|
||||
/* Length of pM (that is, of pMd without the
|
||||
trailing "d"). */
|
||||
|
|
@ -4269,9 +4282,10 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
|
||||
/* Create the copy of the conversion specification, with
|
||||
any width and precision removed, with ".*" inserted,
|
||||
with "L" possibly inserted for floating-point formats,
|
||||
and with pM inserted for integer formats.
|
||||
At most two flags F can be specified at once. */
|
||||
char convspec[sizeof "%FF.*d" + pMlen];
|
||||
char convspec[sizeof "%FF.*d" + max (INT_AS_LDBL, pMlen)];
|
||||
{
|
||||
char *f = convspec;
|
||||
*f++ = '%';
|
||||
|
|
@ -4281,9 +4295,15 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
*f = '#'; f += sharp_flag;
|
||||
*f++ = '.';
|
||||
*f++ = '*';
|
||||
if (conversion == 'd' || conversion == 'i'
|
||||
|| conversion == 'o' || conversion == 'x'
|
||||
|| conversion == 'X')
|
||||
if (float_conversion)
|
||||
{
|
||||
if (INT_AS_LDBL)
|
||||
{
|
||||
*f = 'L';
|
||||
f += INTEGERP (args[n]);
|
||||
}
|
||||
}
|
||||
else if (conversion != 'c')
|
||||
{
|
||||
memcpy (f, pMd, pMlen);
|
||||
f += pMlen;
|
||||
|
|
@ -4310,9 +4330,20 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
not suitable here. */
|
||||
char sprintf_buf[SPRINTF_BUFSIZE];
|
||||
ptrdiff_t sprintf_bytes;
|
||||
if (conversion == 'e' || conversion == 'f' || conversion == 'g')
|
||||
sprintf_bytes = sprintf (sprintf_buf, convspec, prec,
|
||||
XFLOATINT (args[n]));
|
||||
if (float_conversion)
|
||||
{
|
||||
if (INT_AS_LDBL && INTEGERP (args[n]))
|
||||
{
|
||||
/* Although long double may have a rounding error if
|
||||
DIG_BITS_LBOUND * LDBL_MANT_DIG < FIXNUM_BITS - 1,
|
||||
it is more accurate than plain 'double'. */
|
||||
long double x = XINT (args[n]);
|
||||
sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x);
|
||||
}
|
||||
else
|
||||
sprintf_bytes = sprintf (sprintf_buf, convspec, prec,
|
||||
XFLOATINT (args[n]));
|
||||
}
|
||||
else if (conversion == 'c')
|
||||
{
|
||||
/* Don't use sprintf here, as it might mishandle prec. */
|
||||
|
|
@ -4374,8 +4405,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
|
|||
uintmax_t leading_zeros = 0, trailing_zeros = 0;
|
||||
if (excess_precision)
|
||||
{
|
||||
if (conversion == 'e' || conversion == 'f'
|
||||
|| conversion == 'g')
|
||||
if (float_conversion)
|
||||
{
|
||||
if ((conversion == 'g' && ! sharp_flag)
|
||||
|| ! ('0' <= sprintf_buf[sprintf_bytes - 1]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue