summaryrefslogtreecommitdiff
path: root/src/bignum.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2018-09-22 08:59:06 -0700
committerPaul Eggert <eggert@cs.ucla.edu>2018-09-22 09:01:26 -0700
commit0b36041d2a528419982a19940573783ff318c0d4 (patch)
tree0b66b534356f9e4ed7cb39601f6d29492cbea104 /src/bignum.c
parent596ccc087c8f844f81b075da643e5c554a8de9d6 (diff)
downloademacs-0b36041d2a528419982a19940573783ff318c0d4.tar.gz
emacs-0b36041d2a528419982a19940573783ff318c0d4.tar.bz2
emacs-0b36041d2a528419982a19940573783ff318c0d4.zip
Round bignums consistently with other integers
* 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.
Diffstat (limited to 'src/bignum.c')
-rw-r--r--src/bignum.c34
1 files changed, 31 insertions, 3 deletions
diff --git a/src/bignum.c b/src/bignum.c
index 5e86c404b70..1e78d981b7d 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -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.