/* Big numbers for Emacs.
Copyright 2018 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see . */
#include
#include "bignum.h"
#include "lisp.h"
/* Return the value of the Lisp bignum N, as a double. */
double
bignum_to_double (Lisp_Object n)
{
return mpz_get_d (XBIGNUM (n)->value);
}
/* Return D, converted to a bignum. Discard any fraction. */
Lisp_Object
double_to_bignum (double d)
{
mpz_t z;
mpz_init_set_d (z, d);
Lisp_Object result = make_integer (z);
mpz_clear (z);
return result;
}
/* Return a Lisp integer equal to OP, which has BITS bits and which
must not be in fixnum range. */
static Lisp_Object
make_bignum_bits (mpz_t const op, size_t bits)
{
/* The documentation says integer-width should be nonnegative, so
a single comparison suffices even though 'bits' is unsigned. */
if (integer_width < bits)
range_error ();
struct Lisp_Bignum *b = ALLOCATE_PSEUDOVECTOR (struct Lisp_Bignum, value,
PVEC_BIGNUM);
/* We could mpz_init + mpz_swap here, to avoid a copy, but the
resulting API seemed possibly confusing. */
mpz_init_set (b->value, op);
return make_lisp_ptr (b, Lisp_Vectorlike);
}
/* Return a Lisp integer equal to OP, which must not be in fixnum range. */
static Lisp_Object
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)
{
eassert (FIXNUM_OVERFLOW_P (n));
mpz_t z;
mpz_init (z);
mpz_set_intmax (z, n);
Lisp_Object result = make_bignum (z);
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
make_integer (mpz_t const op)
{
size_t bits = mpz_sizeinbase (op, 2);
if (bits <= FIXNUM_BITS)
{
EMACS_INT v = 0;
int i = 0, shift = 0;
do
{
EMACS_INT limb = mpz_getlimbn (op, i++);
v += limb << shift;
shift += GMP_NUMB_BITS;
}
while (shift < bits);
if (mpz_sgn (op) < 0)
v = -v;
if (!FIXNUM_OVERFLOW_P (v))
return make_fixnum (v);
}
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)
{
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. */
Lisp_Object
bignum_to_string (Lisp_Object num, int base)
{
ptrdiff_t n = mpz_sizeinbase (XBIGNUM (num)->value, base) - 1;
USE_SAFE_ALLOCA;
char *str = SAFE_ALLOCA (n + 3);
mpz_get_str (str, base, XBIGNUM (num)->value);
while (str[n])
n++;
Lisp_Object result = make_unibyte_string (str, n);
SAFE_FREE ();
return result;
}
/* Create a bignum by scanning NUM, with digits in BASE.
NUM must consist of an optional '-', a nonempty sequence
of base-BASE digits, and a terminating null byte, and
the represented number must not be in fixnum range. */
Lisp_Object
make_bignum_str (char const *num, int base)
{
struct Lisp_Bignum *b = ALLOCATE_PSEUDOVECTOR (struct Lisp_Bignum, value,
PVEC_BIGNUM);
mpz_init (b->value);
int check = mpz_set_str (b->value, num, base);
eassert (check == 0);
return make_lisp_ptr (b, Lisp_Vectorlike);
}