]> git.eshelyaron.com Git - emacs.git/commitdiff
Round bignums consistently with other integers
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 22 Sep 2018 15:59:06 +0000 (08:59 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 22 Sep 2018 16:01:26 +0000 (09:01 -0700)
* src/bignum.c (mpz_bufsize): New function.
(bignum_bufsize): Use it.
(mpz_get_d_rounded): New function.
(bignum_to_double): Use it.
* src/bignum.c (bignum_to_double):
* src/data.c (bignum_arith_driver):
When converting bignums to double, round instead of
truncating, to be consistent with what happens with fixnums.
* test/src/floatfns-tests.el (bignum-to-float): Test rounding.

src/bignum.c
src/bignum.h
src/data.c
test/src/floatfns-tests.el

index 5e86c404b70a6e2cddc30fc8200b2afa80722cdb..1e78d981b7d07ab03ac9e7418ed1978762e9d574 100644 (file)
@@ -62,7 +62,7 @@ init_bignum (void)
 double
 bignum_to_double (Lisp_Object n)
 {
-  return mpz_get_d (XBIGNUM (n)->value);
+  return mpz_get_d_rounded (XBIGNUM (n)->value);
 }
 
 /* Return D, converted to a Lisp integer.  Discard any fraction.
@@ -251,12 +251,40 @@ bignum_to_uintmax (Lisp_Object x)
 }
 
 /* Yield an upper bound on the buffer size needed to contain a C
-   string representing the bignum NUM in base BASE.  This includes any
+   string representing the NUM in base BASE.  This includes any
    preceding '-' and the terminating null.  */
+static ptrdiff_t
+mpz_bufsize (mpz_t const num, int base)
+{
+  return mpz_sizeinbase (num, base) + 2;
+}
 ptrdiff_t
 bignum_bufsize (Lisp_Object num, int base)
 {
-  return mpz_sizeinbase (XBIGNUM (num)->value, base) + 2;
+  return mpz_bufsize (XBIGNUM (num)->value, base);
+}
+
+/* Convert NUM to a nearest double, as opposed to mpz_get_d which
+   truncates toward zero.  */
+double
+mpz_get_d_rounded (mpz_t const num)
+{
+  ptrdiff_t size = mpz_bufsize (num, 10);
+
+  /* Use mpz_get_d as a shortcut for a bignum so small that rounding
+     errors cannot occur, which is possible if EMACS_INT (not counting
+     sign) has fewer bits than a double significand.  */
+  if (! ((FLT_RADIX == 2 && DBL_MANT_DIG <= FIXNUM_BITS - 1)
+        || (FLT_RADIX == 16 && DBL_MANT_DIG * 4 <= FIXNUM_BITS - 1))
+      && size <= DBL_DIG + 2)
+    return mpz_get_d (num);
+
+  USE_SAFE_ALLOCA;
+  char *buf = SAFE_ALLOCA (size);
+  mpz_get_str (buf, 10, num);
+  double result = strtod (buf, NULL);
+  SAFE_FREE ();
+  return result;
 }
 
 /* Store into BUF (of size SIZE) the value of NUM as a base-BASE string.
index 655154934365b4368322c54f13621a88a3e7e84f..e9cd5c07635981fa8d5423eeadcb467b5f1491e7 100644 (file)
@@ -46,6 +46,7 @@ extern mpz_t mpz[4];
 extern void init_bignum (void);
 extern Lisp_Object make_integer_mpz (void);
 extern void mpz_set_intmax_slow (mpz_t, intmax_t) ARG_NONNULL ((1));
+extern double mpz_get_d_rounded (mpz_t const);
 
 INLINE_HEADER_BEGIN
 
index cc080372d8b8472876d92d14a05dd605c88d1c20..750d494b83af13c98315af833d704b8fef9599d3 100644 (file)
@@ -2921,7 +2921,7 @@ bignum_arith_driver (enum arithop code, ptrdiff_t nargs, Lisp_Object *args,
       CHECK_NUMBER_COERCE_MARKER (val);
       if (FLOATP (val))
        return float_arith_driver (code, nargs, args, argnum,
-                                  mpz_get_d (*accum), val);
+                                  mpz_get_d_rounded (*accum), val);
     }
 }
 
index 14576b603c0241f1e0c9d36728abf3c749877c43..61b1c25743d211ade326b6c2aeb91bd877ee601f 100644 (file)
   (should-error (fround 0) :type 'wrong-type-argument))
 
 (ert-deftest bignum-to-float ()
+  ;; 122 because we want to go as big as possible to provoke a rounding error,
+  ;; but not too big: 2**122 < 10**37 < 2**123, and the C standard says
+  ;; 10**37 <= DBL_MAX so 2**122 cannot overflow as a double.
+  (let ((a (1- (ash 1 122))))
+    (should (or (eql a (1- (floor (float a))))
+                (eql a (floor (float a))))))
   (should (eql (float (+ most-positive-fixnum 1))
                (+ (float most-positive-fixnum) 1))))