emacs-diffs
[Top][All Lists]
Advanced

[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]



reply via email to

[Prev in Thread] Current Thread [Next in Thread]