diff options
Diffstat (limited to 'src/bignum.c')
-rw-r--r-- | src/bignum.c | 123 |
1 files changed, 109 insertions, 14 deletions
diff --git a/src/bignum.c b/src/bignum.c index 18f94e7ed63..5dbfdb9319a 100644 --- a/src/bignum.c +++ b/src/bignum.c @@ -67,6 +67,18 @@ make_bignum (mpz_t const op) return make_bignum_bits (op, mpz_sizeinbase (op, 2)); } +static void mpz_set_uintmax_slow (mpz_t, uintmax_t); + +/* Set RESULT to V. */ +static void +mpz_set_uintmax (mpz_t result, uintmax_t v) +{ + if (v <= ULONG_MAX) + mpz_set_ui (result, v); + else + mpz_set_uintmax_slow (result, v); +} + /* Return a Lisp integer equal to N, which must not be in fixnum range. */ Lisp_Object make_bigint (intmax_t n) @@ -79,6 +91,17 @@ make_bigint (intmax_t n) mpz_clear (z); return result; } +Lisp_Object +make_biguint (uintmax_t n) +{ + eassert (FIXNUM_OVERFLOW_P (n)); + mpz_t z; + mpz_init (z); + mpz_set_uintmax (z, n); + Lisp_Object result = make_bignum (z); + mpz_clear (z); + return result; +} /* Return a Lisp integer with value taken from OP. */ Lisp_Object @@ -109,23 +132,95 @@ make_integer (mpz_t const op) return make_bignum_bits (op, bits); } +/* Set RESULT to V. This code is for when intmax_t is wider than long. */ void mpz_set_intmax_slow (mpz_t result, intmax_t v) { - bool complement = v < 0; - if (complement) - v = -1 - v; - - enum { nails = sizeof v * CHAR_BIT - INTMAX_WIDTH }; -# ifndef HAVE_GMP - /* mini-gmp requires NAILS to be zero, which is true for all - likely Emacs platforms. Sanity-check this. */ - verify (nails == 0); -# endif - - mpz_import (result, 1, -1, sizeof v, 0, nails, &v); - if (complement) - mpz_com (result, result); + int maxlimbs = (INTMAX_WIDTH + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS; + mp_limb_t *limb = mpz_limbs_write (result, maxlimbs); + int n = 0; + uintmax_t u = v; + bool negative = v < 0; + if (negative) + { + uintmax_t two = 2; + u = -u & ((two << (UINTMAX_WIDTH - 1)) - 1); + } + + do + { + limb[n++] = u; + u = GMP_NUMB_BITS < UINTMAX_WIDTH ? u >> GMP_NUMB_BITS : 0; + } + while (u != 0); + + mpz_limbs_finish (result, negative ? -n : n); +} +static void +mpz_set_uintmax_slow (mpz_t result, uintmax_t v) +{ + int maxlimbs = (UINTMAX_WIDTH + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS; + mp_limb_t *limb = mpz_limbs_write (result, maxlimbs); + int n = 0; + + do + { + limb[n++] = v; + v = GMP_NUMB_BITS < INTMAX_WIDTH ? v >> GMP_NUMB_BITS : 0; + } + while (v != 0); + + mpz_limbs_finish (result, n); +} + +/* Return the value of the bignum X if it fits, 0 otherwise. + A bignum cannot be zero, so 0 indicates failure reliably. */ +intmax_t +bignum_to_intmax (Lisp_Object x) +{ + ptrdiff_t bits = mpz_sizeinbase (XBIGNUM (x)->value, 2); + bool negative = mpz_sgn (XBIGNUM (x)->value) < 0; + + if (bits < INTMAX_WIDTH) + { + intmax_t v = 0; + int i = 0, shift = 0; + + do + { + intmax_t limb = mpz_getlimbn (XBIGNUM (x)->value, i++); + v += limb << shift; + shift += GMP_NUMB_BITS; + } + while (shift < bits); + + return negative ? -v : v; + } + return ((bits == INTMAX_WIDTH && INTMAX_MIN < -INTMAX_MAX && negative + && mpz_scan1 (XBIGNUM (x)->value, 0) == INTMAX_WIDTH - 1) + ? INTMAX_MIN : 0); +} +uintmax_t +bignum_to_uintmax (Lisp_Object x) +{ + uintmax_t v = 0; + if (0 <= mpz_sgn (XBIGNUM (x)->value)) + { + ptrdiff_t bits = mpz_sizeinbase (XBIGNUM (x)->value, 2); + if (bits <= UINTMAX_WIDTH) + { + int i = 0, shift = 0; + + do + { + uintmax_t limb = mpz_getlimbn (XBIGNUM (x)->value, i++); + v += limb << shift; + shift += GMP_NUMB_BITS; + } + while (shift < bits); + } + } + return v; } /* Convert NUM to a base-BASE Lisp string. */ |