diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2017-03-03 09:17:51 -0800 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2017-03-03 09:19:08 -0800 |
commit | 74f87fd111904e5156727c72590d6fc4f67e8366 (patch) | |
tree | f6802878c5105def6d6889d5b8f71e4fe9285b79 /src | |
parent | f1fe3fcfc568c1527714ff3a95e678816e2787d4 (diff) | |
download | emacs-74f87fd111904e5156727c72590d6fc4f67e8366.tar.gz |
logb now works correctly on large integers
* admin/merge-gnulib (GNULIB_MODULES): Add count-leading-zeros.
* etc/NEWS: Document the change.
* lib/count-leading-zeros.c, lib/count-leading-zeros.h:
* m4/count-leading-zeros.m4: New files, copied from Gnulib.
* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
* src/floatfns.c: Include count-leading-zeros.h.
(Flogb): Do not convert fixnum to float before taking the log,
as the rounding error can cause the answer to be off by 1.
* src/lisp.h (EMACS_UINT_WIDTH): New constant.
* test/src/floatfns-tests.el (logb-extreme-fixnum): New test.
Diffstat (limited to 'src')
-rw-r--r-- | src/floatfns.c | 42 | ||||
-rw-r--r-- | src/lisp.h | 6 |
2 files changed, 34 insertions, 14 deletions
diff --git a/src/floatfns.c b/src/floatfns.c index dda03698093..94da22a3ba7 100644 --- a/src/floatfns.c +++ b/src/floatfns.c @@ -45,6 +45,8 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include <math.h> +#include <count-leading-zeros.h> + /* 'isfinite' and 'isnan' cause build failures on Solaris 10 with the bundled GCC in c99 mode. Work around the bugs with simple implementations that are good enough. */ @@ -290,28 +292,46 @@ DEFUN ("float", Ffloat, Sfloat, 1, 1, 0, return arg; } +static int +ecount_leading_zeros (EMACS_UINT x) +{ + return (EMACS_UINT_WIDTH == UINT_WIDTH ? count_leading_zeros (x) + : EMACS_UINT_WIDTH == ULONG_WIDTH ? count_leading_zeros_l (x) + : count_leading_zeros_ll (x)); +} + DEFUN ("logb", Flogb, Slogb, 1, 1, 0, doc: /* Returns largest integer <= the base 2 log of the magnitude of ARG. This is the same as the exponent of a float. */) (Lisp_Object arg) { - Lisp_Object val; EMACS_INT value; - double f = extract_float (arg); + CHECK_NUMBER_OR_FLOAT (arg); - if (f == 0.0) - value = MOST_NEGATIVE_FIXNUM; - else if (isfinite (f)) + if (FLOATP (arg)) { - int ivalue; - frexp (f, &ivalue); - value = ivalue - 1; + double f = XFLOAT_DATA (arg); + + if (f == 0) + value = MOST_NEGATIVE_FIXNUM; + else if (isfinite (f)) + { + int ivalue; + frexp (f, &ivalue); + value = ivalue - 1; + } + else + value = MOST_POSITIVE_FIXNUM; } else - value = MOST_POSITIVE_FIXNUM; + { + EMACS_INT i = eabs (XINT (arg)); + value = (i == 0 + ? MOST_NEGATIVE_FIXNUM + : EMACS_UINT_WIDTH - 1 - ecount_leading_zeros (i)); + } - XSETINT (val, value); - return val; + return make_number (value); } diff --git a/src/lisp.h b/src/lisp.h index 220188cdb87..6d0b5283356 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -80,19 +80,19 @@ DEFINE_GDB_SYMBOL_END (GCTYPEBITS) # elif INTPTR_MAX <= INT_MAX && !defined WIDE_EMACS_INT typedef int EMACS_INT; typedef unsigned int EMACS_UINT; -enum { EMACS_INT_WIDTH = INT_WIDTH }; +enum { EMACS_INT_WIDTH = INT_WIDTH, EMACS_UINT_WIDTH = UINT_WIDTH }; # define EMACS_INT_MAX INT_MAX # define pI "" # elif INTPTR_MAX <= LONG_MAX && !defined WIDE_EMACS_INT typedef long int EMACS_INT; typedef unsigned long EMACS_UINT; -enum { EMACS_INT_WIDTH = LONG_WIDTH }; +enum { EMACS_INT_WIDTH = LONG_WIDTH, EMACS_UINT_WIDTH = ULONG_WIDTH }; # define EMACS_INT_MAX LONG_MAX # define pI "l" # elif INTPTR_MAX <= LLONG_MAX typedef long long int EMACS_INT; typedef unsigned long long int EMACS_UINT; -enum { EMACS_INT_WIDTH = LLONG_WIDTH }; +enum { EMACS_INT_WIDTH = LLONG_WIDTH, EMACS_UINT_WIDTH = ULLONG_WIDTH }; # define EMACS_INT_MAX LLONG_MAX # ifdef __MINGW32__ # define pI "I64" |