[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] master 44e7ee2 3/4: Fewer rounding errors with (format "%f
From: |
Paul Eggert |
Subject: |
[Emacs-diffs] master 44e7ee2 3/4: Fewer rounding errors with (format "%f" fixnum) |
Date: |
Sun, 5 Mar 2017 02:18:45 -0500 (EST) |
branch: master
commit 44e7ee2e356452139156e8175c46f646835d27ff
Author: Paul Eggert <address@hidden>
Commit: Paul Eggert <address@hidden>
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.
---
etc/NEWS | 8 ++++++++
src/editfns.c | 56 +++++++++++++++++++++++++++++++++++++++++++-------------
2 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index a8db54c..9c99593 100644
--- a/etc/NEWS
+++ b/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.
diff --git a/src/editfns.c b/src/editfns.c
index db9ad06..612893c 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -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]