aboutsummaryrefslogtreecommitdiffstats
path: root/mpfr.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpfr.c')
-rw-r--r--mpfr.c1636
1 files changed, 1636 insertions, 0 deletions
diff --git a/mpfr.c b/mpfr.c
new file mode 100644
index 00000000..a147150e
--- /dev/null
+++ b/mpfr.c
@@ -0,0 +1,1636 @@
+/*
+ * mpfr.c - routines for arbitrary-precision number support in gawk.
+ */
+
+/*
+ * Copyright (C) 2012 the Free Software Foundation, Inc.
+ *
+ * This file is part of GAWK, the GNU implementation of the
+ * AWK Programming Language.
+ *
+ * GAWK 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.
+ *
+ * GAWK 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "awk.h"
+
+#ifdef HAVE_MPFR
+
+#if !defined(MPFR_VERSION_MAJOR) || MPFR_VERSION_MAJOR < 3
+typedef mp_exp_t mpfr_exp_t;
+#endif
+
+extern NODE **fmt_list; /* declared in eval.c */
+
+mpz_t mpzval; /* GMP integer type, used as temporary in few places */
+mpz_t MNR;
+mpz_t MFNR;
+bool do_ieee_fmt; /* IEEE-754 floating-point emulation */
+mpfr_rnd_t ROUND_MODE;
+
+static mpfr_rnd_t get_rnd_mode(const char rmode);
+static NODE *mpg_force_number(NODE *n);
+static NODE *mpg_make_number(double);
+static NODE *mpg_format_val(const char *format, int index, NODE *s);
+static int mpg_interpret(INSTRUCTION **cp);
+
+static mpfr_exp_t min_exp = MPFR_EMIN_DEFAULT;
+static mpfr_exp_t max_exp = MPFR_EMAX_DEFAULT;
+
+/* temporaries used in bit ops */
+static NODE *_tz1;
+static NODE *_tz2;
+static mpz_t _mpz1;
+static mpz_t _mpz2;
+static mpz_ptr mpz1;
+static mpz_ptr mpz2;
+
+static NODE *get_bit_ops(const char *op);
+#define free_bit_ops() (DEREF(_tz1), DEREF(_tz2))
+
+/* temporary MPFR floats used to hold converted GMP integer operands */
+static mpfr_t _mpf_t1;
+static mpfr_t _mpf_t2;
+
+/*
+ * PRECISION_MIN is the precision used to initialize _mpf_t1 and _mpf_t2.
+ * 64 bits should be enough for exact conversion of most integers to floats.
+ */
+
+#define PRECISION_MIN 64
+
+/* mf = { _mpf_t1, _mpf_t2 } */
+static inline mpfr_ptr mpg_tofloat(mpfr_ptr mf, mpz_ptr mz);
+/* T = {t1, t2} */
+#define MP_FLOAT(T) is_mpg_integer(T) ? mpg_tofloat(_mpf_##T, (T)->mpg_i) : (T)->mpg_numbr
+
+
+/* init_mpfr --- set up MPFR related variables */
+
+void
+init_mpfr(mpfr_prec_t prec, const char *rmode)
+{
+ mpfr_set_default_prec(prec);
+ ROUND_MODE = get_rnd_mode(rmode[0]);
+ mpfr_set_default_rounding_mode(ROUND_MODE);
+ make_number = mpg_make_number;
+ str2number = mpg_force_number;
+ format_val = mpg_format_val;
+ cmp_numbers = mpg_cmp;
+
+ mpz_init(MNR);
+ mpz_init(MFNR);
+ do_ieee_fmt = false;
+
+ mpz_init(_mpz1);
+ mpz_init(_mpz2);
+ mpfr_init2(_mpf_t1, PRECISION_MIN);
+ mpfr_init2(_mpf_t2, PRECISION_MIN);
+ mpz_init(mpzval);
+
+ register_exec_hook(mpg_interpret, 0);
+}
+
+/* mpg_node --- allocate a node to store MPFR float or GMP integer */
+
+NODE *
+mpg_node(unsigned int tp)
+{
+ NODE *r;
+ getnode(r);
+ r->type = Node_val;
+
+ if (tp == MPFN) {
+ /* Initialize, set precision to the default precision, and value to NaN */
+ mpfr_init(r->mpg_numbr);
+ r->flags = MPFN;
+ } else {
+ /* Initialize and set value to 0 */
+ mpz_init(r->mpg_i);
+ r->flags = MPZN;
+ }
+
+ r->valref = 1;
+ r->flags |= MALLOC|NUMBER|NUMCUR;
+ r->stptr = NULL;
+ r->stlen = 0;
+#if MBS_SUPPORT
+ r->wstptr = NULL;
+ r->wstlen = 0;
+#endif /* defined MBS_SUPPORT */
+ return r;
+}
+
+/*
+ * mpg_make_number --- make a arbitrary-precision number node
+ * and initialize with a C double
+ */
+
+static NODE *
+mpg_make_number(double x)
+{
+ NODE *r;
+ double ival;
+
+ if ((ival = double_to_int(x)) != x) {
+ int tval;
+ r = mpg_float();
+ tval = mpfr_set_d(r->mpg_numbr, x, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ } else {
+ r = mpg_integer();
+ mpz_set_d(r->mpg_i, ival);
+ }
+ return r;
+}
+
+/* mpg_strtoui --- assign arbitrary-precision integral value from a string */
+
+int
+mpg_strtoui(mpz_ptr zi, char *str, size_t len, char **end, int base)
+{
+ char *s = str;
+ char *start;
+ int ret = -1;
+
+ /*
+ * mpz_set_str does not like leading 0x or 0X for hex (or 0 for octal)
+ * with a non-zero base argument.
+ */
+ if (base == 16 && len >= 2 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) {
+ s += 2; len -= 2;
+ } else if (base == 8 && len >= 1 && *s == '0') {
+ s++; len--;
+ }
+ start = s;
+
+ while (len > 0) {
+ switch (*s) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ break;
+ case '8':
+ case '9':
+ if (base == 8)
+ goto done;
+ break;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ if (base == 16)
+ break;
+ default:
+ goto done;
+ }
+ s++; len--;
+ }
+done:
+ if (s > start) {
+ char save = *s;
+ *s = '\0';
+ ret = mpz_set_str(zi, start, base);
+ *s = save;
+ }
+ if (end != NULL)
+ *end = s;
+ return ret;
+}
+
+
+/* mpg_maybe_float --- test if a string may contain arbitrary-precision float */
+
+static int
+mpg_maybe_float(const char *str, int use_locale)
+{
+ int dec_point = '.';
+ const char *s = str;
+
+#if defined(HAVE_LOCALE_H)
+ /*
+ * loc.decimal_point may not have been initialized yet,
+ * so double check it before using it.
+ */
+ if (use_locale && loc.decimal_point != NULL && loc.decimal_point[0] != '\0')
+ dec_point = loc.decimal_point[0]; /* XXX --- assumes one char */
+#endif
+
+ if (strlen(s) >= 3
+ && ( ( (s[0] == 'i' || s[0] == 'I')
+ && (s[1] == 'n' || s[1] == 'N')
+ && (s[2] == 'f' || s[2] == 'F'))
+ || ( (s[0] == 'n' || s[0] == 'N')
+ && (s[1] == 'a' || s[1] == 'A')
+ && (s[2] == 'n' || s[2] == 'N'))))
+ return true;
+
+ for (; *s != '\0'; s++) {
+ if (*s == dec_point || *s == 'e' || *s == 'E')
+ return true;
+ }
+
+ return false;
+}
+
+
+/* mpg_zero --- initialize with arbitrary-precision integer(GMP) and set value to zero */
+
+static inline void
+mpg_zero(NODE *n)
+{
+ if (is_mpg_float(n)) {
+ mpfr_clear(n->mpg_numbr);
+ n->flags &= ~MPFN;
+ }
+ if (! is_mpg_integer(n)) {
+ mpz_init(n->mpg_i); /* this also sets its value to 0 */
+ n->flags |= MPZN;
+ } else
+ mpz_set_si(n->mpg_i, 0);
+}
+
+
+/* force_mpnum --- force a value to be a GMP integer or MPFR float */
+
+static int
+force_mpnum(NODE *n, int do_nondec, int use_locale)
+{
+ char *cp, *cpend, *ptr, *cp1;
+ char save;
+ int tval, base = 10;
+
+ if (n->stlen == 0) {
+ mpg_zero(n);
+ return false;
+ }
+
+ cp = n->stptr;
+ cpend = n->stptr + n->stlen;
+ while (cp < cpend && isspace((unsigned char) *cp))
+ cp++;
+ if (cp == cpend) { /* only spaces */
+ mpg_zero(n);
+ return false;
+ }
+
+ save = *cpend;
+ *cpend = '\0';
+
+ if (*cp == '+' || *cp == '-')
+ cp1 = cp + 1;
+ else
+ cp1 = cp;
+
+ if (do_nondec)
+ base = get_numbase(cp1, use_locale);
+
+ if (! mpg_maybe_float(cp1, use_locale)) {
+ mpg_zero(n);
+ errno = 0;
+ mpg_strtoui(n->mpg_i, cp1, cpend - cp1, & ptr, base);
+ if (*cp == '-')
+ mpz_neg(n->mpg_i, n->mpg_i);
+ goto done;
+ }
+
+ if (is_mpg_integer(n)) {
+ mpz_clear(n->mpg_i);
+ n->flags &= ~MPZN;
+ }
+
+ if (! is_mpg_float(n)) {
+ mpfr_init(n->mpg_numbr);
+ n->flags |= MPFN;
+ }
+
+ errno = 0;
+ tval = mpfr_strtofr(n->mpg_numbr, cp, & ptr, base, ROUND_MODE);
+ IEEE_FMT(n->mpg_numbr, tval);
+done:
+ /* trailing space is OK for NUMBER */
+ while (isspace((unsigned char) *ptr))
+ ptr++;
+ *cpend = save;
+ if (errno == 0 && ptr == cpend)
+ return true;
+ errno = 0;
+ return false;
+}
+
+/* mpg_force_number --- force a value to be a multiple-precision number */
+
+static NODE *
+mpg_force_number(NODE *n)
+{
+ unsigned int newflags = 0;
+
+ if (is_mpg_number(n) && (n->flags & NUMCUR))
+ return n;
+
+ if (n->flags & MAYBE_NUM) {
+ n->flags &= ~MAYBE_NUM;
+ newflags = NUMBER;
+ }
+
+ if (force_mpnum(n, (do_non_decimal_data && ! do_traditional), true)) {
+ n->flags |= newflags;
+ n->flags |= NUMCUR;
+ }
+ return n;
+}
+
+/* mpg_format_val --- format a numeric value based on format */
+
+static NODE *
+mpg_format_val(const char *format, int index, NODE *s)
+{
+ NODE *dummy[2], *r;
+ unsigned int oflags;
+
+ /* create dummy node for a sole use of format_tree */
+ dummy[1] = s;
+ oflags = s->flags;
+
+ if (is_mpg_integer(s) || mpfr_integer_p(s->mpg_numbr)) {
+ /* integral value, use %d */
+ r = format_tree("%d", 2, dummy, 2);
+ s->stfmt = -1;
+ } else {
+ r = format_tree(format, fmt_list[index]->stlen, dummy, 2);
+ assert(r != NULL);
+ s->stfmt = (char) index;
+ }
+ s->flags = oflags;
+ s->stlen = r->stlen;
+ if ((s->flags & STRCUR) != 0)
+ efree(s->stptr);
+ s->stptr = r->stptr;
+ freenode(r); /* Do not unref(r)! We want to keep s->stptr == r->stpr. */
+
+ s->flags |= STRCUR;
+ free_wstr(s);
+ return s;
+}
+
+/* mpg_cmp --- compare two numbers */
+
+int
+mpg_cmp(const NODE *t1, const NODE *t2)
+{
+ /*
+ * For the purposes of sorting, NaN is considered greater than
+ * any other value, and all NaN values are considered equivalent and equal.
+ */
+
+ if (is_mpg_float(t1)) {
+ if (is_mpg_float(t2)) {
+ if (mpfr_nan_p(t1->mpg_numbr))
+ return ! mpfr_nan_p(t2->mpg_numbr);
+ if (mpfr_nan_p(t2->mpg_numbr))
+ return -1;
+ return mpfr_cmp(t1->mpg_numbr, t2->mpg_numbr);
+ }
+ if (mpfr_nan_p(t1->mpg_numbr))
+ return 1;
+ return mpfr_cmp_z(t1->mpg_numbr, t2->mpg_i);
+ } else if (is_mpg_float(t2)) {
+ int ret;
+ if (mpfr_nan_p(t2->mpg_numbr))
+ return -1;
+ ret = mpfr_cmp_z(t2->mpg_numbr, t1->mpg_i);
+ return ret > 0 ? -1 : (ret < 0);
+ } else if (is_mpg_integer(t1)) {
+ return mpz_cmp(t1->mpg_i, t2->mpg_i);
+ }
+
+ /* t1 and t2 are AWKNUMs */
+ return cmp_awknums(t1, t2);
+}
+
+
+/*
+ * mpg_update_var --- update NR or FNR.
+ * NR_node->var_value(mpz_t) = MNR(mpz_t) * LONG_MAX + NR(long)
+ */
+
+NODE *
+mpg_update_var(NODE *n)
+{
+ NODE *val = n->var_value;
+ long nr = 0;
+ mpz_ptr nq = 0;
+
+ if (n == NR_node) {
+ nr = NR;
+ nq = MNR;
+ } else if (n == FNR_node) {
+ nr = FNR;
+ nq = MFNR;
+ } else
+ cant_happen();
+
+ if (mpz_sgn(nq) == 0) {
+ /* Efficiency hack similar to that for AWKNUM */
+ if (is_mpg_float(val) || mpz_get_si(val->mpg_i) != nr) {
+ unref(n->var_value);
+ val = n->var_value = mpg_integer();
+ mpz_set_si(val->mpg_i, nr);
+ }
+ } else {
+ unref(n->var_value);
+ val = n->var_value = mpg_integer();
+ mpz_set_si(val->mpg_i, nr);
+ mpz_addmul_ui(val->mpg_i, nq, LONG_MAX); /* val->mpg_i += nq * LONG_MAX */
+ }
+ return val;
+}
+
+/* mpg_set_var --- set NR or FNR */
+
+long
+mpg_set_var(NODE *n)
+{
+ long nr = 0;
+ mpz_ptr nq = 0, r;
+ NODE *val = n->var_value;
+
+ if (n == NR_node)
+ nq = MNR;
+ else if (n == FNR_node)
+ nq = MFNR;
+ else
+ cant_happen();
+
+ if (is_mpg_integer(val))
+ r = val->mpg_i;
+ else {
+ /* convert float to integer */
+ mpfr_get_z(mpzval, val->mpg_numbr, MPFR_RNDZ);
+ r = mpzval;
+ }
+ nr = mpz_fdiv_q_ui(nq, r, LONG_MAX); /* nq (MNR or MFNR) is quotient */
+ return nr; /* remainder (NR or FNR) */
+}
+
+/* set_PREC --- update MPFR PRECISION related variables when PREC assigned to */
+
+void
+set_PREC()
+{
+ long prec = 0;
+ NODE *val;
+ static const struct ieee_fmt {
+ const char *name;
+ mpfr_prec_t precision;
+ mpfr_exp_t emax;
+ mpfr_exp_t emin;
+ } ieee_fmts[] = {
+{ "half", 11, 16, -23 }, /* binary16 */
+{ "single", 24, 128, -148 }, /* binary32 */
+{ "double", 53, 1024, -1073 }, /* binary64 */
+{ "quad", 113, 16384, -16493 }, /* binary128 */
+{ "oct", 237, 262144, -262377 }, /* binary256, not in the IEEE 754-2008 standard */
+
+ /*
+ * For any bitwidth = 32 * k ( k >= 4),
+ * precision = 13 + bitwidth - int(4 * log2(bitwidth))
+ * emax = 1 << bitwidth - precision - 1
+ * emin = 4 - emax - precision
+ */
+ };
+
+ if (! do_mpfr)
+ return;
+
+ val = PREC_node->var_value;
+ if (val->flags & MAYBE_NUM)
+ force_number(val);
+
+ if ((val->flags & (STRING|NUMBER)) == STRING) {
+ int i, j;
+
+ /* emulate IEEE-754 binary format */
+
+ for (i = 0, j = sizeof(ieee_fmts)/sizeof(ieee_fmts[0]); i < j; i++) {
+ if (strcasecmp(ieee_fmts[i].name, val->stptr) == 0)
+ break;
+ }
+
+ if (i < j) {
+ prec = ieee_fmts[i].precision;
+
+ /*
+ * We *DO NOT* change the MPFR exponent range using
+ * mpfr_set_{emin, emax} here. See format_ieee() for details.
+ */
+ max_exp = ieee_fmts[i].emax;
+ min_exp = ieee_fmts[i].emin;
+
+ do_ieee_fmt = true;
+ }
+ }
+
+ if (prec <= 0) {
+ force_number(val);
+ prec = get_number_si(val);
+ if (prec < MPFR_PREC_MIN || prec > MPFR_PREC_MAX) {
+ force_string(val);
+ warning(_("PREC value `%.*s' is invalid"), (int) val->stlen, val->stptr);
+ prec = 0;
+ } else
+ do_ieee_fmt = false;
+ }
+
+ if (prec > 0)
+ mpfr_set_default_prec(prec);
+}
+
+
+/* get_rnd_mode --- convert string to MPFR rounding mode */
+
+static mpfr_rnd_t
+get_rnd_mode(const char rmode)
+{
+ switch (rmode) {
+ case 'N':
+ case 'n':
+ return MPFR_RNDN; /* round to nearest (IEEE-754 roundTiesToEven) */
+ case 'Z':
+ case 'z':
+ return MPFR_RNDZ; /* round toward zero (IEEE-754 roundTowardZero) */
+ case 'U':
+ case 'u':
+ return MPFR_RNDU; /* round toward plus infinity (IEEE-754 roundTowardPositive) */
+ case 'D':
+ case 'd':
+ return MPFR_RNDD; /* round toward minus infinity (IEEE-754 roundTowardNegative) */
+#if defined(MPFR_VERSION_MAJOR) && MPFR_VERSION_MAJOR > 2
+ case 'A':
+ case 'a':
+ return MPFR_RNDA; /* round away from zero (IEEE-754 roundTiesToAway) */
+#endif
+ default:
+ break;
+ }
+ return -1;
+}
+
+/*
+ * set_ROUNDMODE --- update MPFR rounding mode related variables
+ * when ROUNDMODE assigned to
+ */
+
+void
+set_ROUNDMODE()
+{
+ if (do_mpfr) {
+ mpfr_rnd_t rndm = -1;
+ NODE *n;
+ n = force_string(ROUNDMODE_node->var_value);
+ if (n->stlen == 1)
+ rndm = get_rnd_mode(n->stptr[0]);
+ if (rndm != -1) {
+ mpfr_set_default_rounding_mode(rndm);
+ ROUND_MODE = rndm;
+ } else
+ warning(_("RNDMODE value `%.*s' is invalid"), (int) n->stlen, n->stptr);
+ }
+}
+
+
+/* format_ieee --- make sure a number follows IEEE-754 floating-point standard */
+
+int
+format_ieee(mpfr_ptr x, int tval)
+{
+ /*
+ * The MPFR doc says that it's our responsibility to make sure all numbers
+ * including those previously created are in range after we've changed the
+ * exponent range. Most MPFR operations and functions require
+ * the input arguments to have exponents within the current exponent range.
+ * Any argument outside the range results in a MPFR assertion failure
+ * like this:
+ *
+ * $ gawk -M 'BEGIN { x=1.0e-10000; print x+0; PREC="double"; print x+0}'
+ * 1e-10000
+ * init2.c:52: MPFR assertion failed ....
+ *
+ * A "naive" approach would be to keep track of the ternary state and
+ * the rounding mode for each number, and make sure it is in the current
+ * exponent range (using mpfr_check_range) before using it in an
+ * operation or function. Instead, we adopt the following strategy.
+ *
+ * When gawk starts, the exponent range is the MPFR default
+ * [MPFR_EMIN_DEFAULT, MPFR_EMAX_DEFAULT]. Any number that gawk
+ * creates must have exponent in this range (excluding infinities, NaNs and zeros).
+ * Each MPFR operation or function is performed with this default exponent
+ * range.
+ *
+ * When emulating IEEE-754 format, the exponents are *temporarily* changed,
+ * mpfr_check_range is called to make sure the number is in the new range,
+ * and mpfr_subnormalize is used to round following the rules of subnormal
+ * arithmetic. The exponent range is then *restored* to the original value
+ * [MPFR_EMIN_DEFAULT, MPFR_EMAX_DEFAULT].
+ */
+
+ (void) mpfr_set_emin(min_exp);
+ (void) mpfr_set_emax(max_exp);
+ tval = mpfr_check_range(x, tval, ROUND_MODE);
+ tval = mpfr_subnormalize(x, tval, ROUND_MODE);
+ (void) mpfr_set_emin(MPFR_EMIN_DEFAULT);
+ (void) mpfr_set_emax(MPFR_EMAX_DEFAULT);
+ return tval;
+}
+
+
+/* do_mpfr_atan2 --- do the atan2 function */
+
+NODE *
+do_mpfr_atan2(int nargs)
+{
+ NODE *t1, *t2, *res;
+ mpfr_ptr p1, p2;
+ int tval;
+
+ t2 = POP_SCALAR();
+ t1 = POP_SCALAR();
+
+ if (do_lint) {
+ if ((t1->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("atan2: received non-numeric first argument"));
+ if ((t2->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("atan2: received non-numeric second argument"));
+ }
+ force_number(t1);
+ force_number(t2);
+
+ p1 = MP_FLOAT(t1);
+ p2 = MP_FLOAT(t2);
+ res = mpg_float();
+ /* See MPFR documentation for handling of special values like +inf as an argument */
+ tval = mpfr_atan2(res->mpg_numbr, p1, p2, ROUND_MODE);
+ IEEE_FMT(res->mpg_numbr, tval);
+
+ DEREF(t1);
+ DEREF(t2);
+ return res;
+}
+
+
+#define SPEC_MATH(X) \
+NODE *t1, *res; \
+mpfr_ptr p1; \
+int tval; \
+t1 = POP_SCALAR(); \
+if (do_lint && (t1->flags & (NUMCUR|NUMBER)) == 0) \
+ lintwarn(_("%s: received non-numeric argument"), #X); \
+force_number(t1); \
+p1 = MP_FLOAT(t1); \
+res = mpg_float(); \
+tval = mpfr_##X(res->mpg_numbr, p1, ROUND_MODE); \
+IEEE_FMT(res->mpg_numbr, tval); \
+DEREF(t1); \
+return res
+
+
+/* do_mpfr_sin --- do the sin function */
+
+NODE *
+do_mpfr_sin(int nargs)
+{
+ SPEC_MATH(sin);
+}
+
+/* do_mpfr_cos --- do the cos function */
+
+NODE *
+do_mpfr_cos(int nargs)
+{
+ SPEC_MATH(cos);
+}
+
+/* do_mpfr_exp --- exponential function */
+
+NODE *
+do_mpfr_exp(int nargs)
+{
+ SPEC_MATH(exp);
+}
+
+/* do_mpfr_log --- the log function */
+
+NODE *
+do_mpfr_log(int nargs)
+{
+ SPEC_MATH(log);
+}
+
+/* do_mpfr_sqrt --- do the sqrt function */
+
+NODE *
+do_mpfr_sqrt(int nargs)
+{
+ SPEC_MATH(sqrt);
+}
+
+/* do_mpfr_int --- convert double to int for awk */
+
+NODE *
+do_mpfr_int(int nargs)
+{
+ NODE *tmp, *r;
+
+ tmp = POP_SCALAR();
+ if (do_lint && (tmp->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("int: received non-numeric argument"));
+ force_number(tmp);
+
+ if (is_mpg_integer(tmp)) {
+ r = mpg_integer();
+ mpz_set(r->mpg_i, tmp->mpg_i);
+ } else {
+ if (! mpfr_number_p(tmp->mpg_numbr)) {
+ /* [+-]inf or NaN */
+ return tmp;
+ }
+
+ r = mpg_integer();
+ mpfr_get_z(r->mpg_i, tmp->mpg_numbr, MPFR_RNDZ);
+ }
+
+ DEREF(tmp);
+ return r;
+}
+
+/* do_mpfr_compl --- perform a ~ operation */
+
+NODE *
+do_mpfr_compl(int nargs)
+{
+ NODE *tmp, *r;
+ mpz_ptr zptr;
+
+ tmp = POP_SCALAR();
+ if (do_lint && (tmp->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("compl: received non-numeric argument"));
+
+ force_number(tmp);
+ if (is_mpg_float(tmp)) {
+ mpfr_ptr p = tmp->mpg_numbr;
+
+ if (! mpfr_number_p(p)) {
+ /* [+-]inf or NaN */
+ return tmp;
+ }
+ if (do_lint) {
+ if (mpfr_sgn(p) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("compl(%Rg): negative value will give strange results"), p)
+ );
+ if (! mpfr_integer_p(p))
+ lintwarn("%s",
+ mpg_fmt(_("comp(%Rg): fractional value will be truncated"), p)
+ );
+ }
+
+ mpfr_get_z(mpzval, p, MPFR_RNDZ); /* float to integer conversion */
+ zptr = mpzval;
+ } else {
+ /* (tmp->flags & MPZN) != 0 */
+ zptr = tmp->mpg_i;
+ if (do_lint) {
+ if (mpz_sgn(zptr) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("cmpl(%Zd): negative values will give strange results"), zptr)
+ );
+ }
+ }
+
+ r = mpg_integer();
+ mpz_com(r->mpg_i, zptr);
+ DEREF(tmp);
+ return r;
+}
+
+
+/*
+ * get_bit_ops --- get the numeric operands of a binary function.
+ * Returns a copy of the operand if either is inf or nan. Otherwise
+ * each operand is converted to an integer if necessary, and
+ * the results are placed in the variables mpz1 and mpz2.
+ */
+
+static NODE *
+get_bit_ops(const char *op)
+{
+ _tz2 = POP_SCALAR();
+ _tz1 = POP_SCALAR();
+
+ if (do_lint) {
+ if ((_tz1->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("%s: received non-numeric first argument"), op);
+ if ((_tz2->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("%s: received non-numeric second argument"), op);
+ }
+
+ force_number(_tz1);
+ force_number(_tz2);
+
+ if (is_mpg_float(_tz1)) {
+ mpfr_ptr left = _tz1->mpg_numbr;
+ if (! mpfr_number_p(left)) {
+ /* inf or NaN */
+ NODE *res;
+ res = mpg_float();
+ mpfr_set(res->mpg_numbr, _tz1->mpg_numbr, ROUND_MODE);
+ return res;
+ }
+
+ if (do_lint) {
+ if (mpfr_sgn(left) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("%s(%Rg, ..): negative values will give strange results"),
+ op, left)
+ );
+ if (! mpfr_integer_p(left))
+ lintwarn("%s",
+ mpg_fmt(_("%s(%Rg, ..): fractional values will be truncated"),
+ op, left)
+ );
+ }
+
+ mpfr_get_z(_mpz1, left, MPFR_RNDZ); /* float to integer conversion */
+ mpz1 = _mpz1;
+ } else {
+ /* (_tz1->flags & MPZN) != 0 */
+ mpz1 = _tz1->mpg_i;
+ if (do_lint) {
+ if (mpz_sgn(mpz1) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("%s(%Zd, ..): negative values will give strange results"),
+ op, mpz1)
+ );
+ }
+ }
+
+ if (is_mpg_float(_tz2)) {
+ mpfr_ptr right = _tz2->mpg_numbr;
+ if (! mpfr_number_p(right)) {
+ /* inf or NaN */
+ NODE *res;
+ res = mpg_float();
+ mpfr_set(res->mpg_numbr, _tz2->mpg_numbr, ROUND_MODE);
+ return res;
+ }
+
+ if (do_lint) {
+ if (mpfr_sgn(right) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("%s(.., %Rg): negative values will give strange results"),
+ op, right)
+ );
+ if (! mpfr_integer_p(right))
+ lintwarn("%s",
+ mpg_fmt(_("%s(.., %Rg): fractional values will be truncated"),
+ op, right)
+ );
+ }
+
+ mpfr_get_z(_mpz2, right, MPFR_RNDZ); /* float to integer conversion */
+ mpz2 = _mpz2;
+ } else {
+ /* (_tz2->flags & MPZN) != 0 */
+ mpz2 = _tz2->mpg_i;
+ if (do_lint) {
+ if (mpz_sgn(mpz2) < 0)
+ lintwarn("%s",
+ mpg_fmt(_("%s(.., %Zd): negative values will give strange results"),
+ op, mpz2)
+ );
+ }
+ }
+
+ return NULL;
+}
+
+/* do_mpfr_lshift --- perform a << operation */
+
+NODE *
+do_mpfr_lshift(int nargs)
+{
+ NODE *res;
+ unsigned long shift;
+
+ if ((res = get_bit_ops("lshift")) == NULL) {
+
+ /*
+ * mpz_get_ui: If op is too big to fit an unsigned long then just
+ * the least significant bits that do fit are returned.
+ * The sign of op is ignored, only the absolute value is used.
+ */
+
+ shift = mpz_get_ui(mpz2); /* GMP integer => unsigned long conversion */
+ res = mpg_integer();
+ mpz_mul_2exp(res->mpg_i, mpz1, shift); /* res = mpz1 * 2^shift */
+ }
+ free_bit_ops();
+ return res;
+}
+
+/* do_mpfr_rshift --- perform a >> operation */
+
+NODE *
+do_mpfr_rhift(int nargs)
+{
+ NODE *res;
+ unsigned long shift;
+
+ if ((res = get_bit_ops("rshift")) == NULL) {
+ /*
+ * mpz_get_ui: If op is too big to fit an unsigned long then just
+ * the least significant bits that do fit are returned.
+ * The sign of op is ignored, only the absolute value is used.
+ */
+
+ shift = mpz_get_ui(mpz2); /* GMP integer => unsigned long conversion */
+ res = mpg_integer();
+ mpz_fdiv_q_2exp(res->mpg_i, mpz1, shift); /* res = mpz1 / 2^shift, round towards −inf */
+ }
+ free_bit_ops();
+ return res;
+}
+
+/* do_mpfr_and --- perform an & operation */
+
+NODE *
+do_mpfr_and(int nargs)
+{
+ NODE *res;
+
+ if ((res = get_bit_ops("and")) == NULL) {
+ res = mpg_integer();
+ mpz_and(res->mpg_i, mpz1, mpz2);
+ }
+ free_bit_ops();
+ return res;
+}
+
+/* do_mpfr_or --- perform an | operation */
+
+NODE *
+do_mpfr_or(int nargs)
+{
+ NODE *res;
+
+ if ((res = get_bit_ops("or")) == NULL) {
+ res = mpg_integer();
+ mpz_ior(res->mpg_i, mpz1, mpz2);
+ }
+ free_bit_ops();
+ return res;
+}
+
+/* do_mpfr_strtonum --- the strtonum function */
+
+NODE *
+do_mpfr_strtonum(int nargs)
+{
+ NODE *tmp, *r;
+
+ tmp = POP_SCALAR();
+ if ((tmp->flags & (NUMBER|NUMCUR)) == 0) {
+ r = mpg_integer(); /* will be changed to MPFR float if necessary in force_mpnum() */
+ r->stptr = tmp->stptr;
+ r->stlen = tmp->stlen;
+ force_mpnum(r, true, use_lc_numeric);
+ r->stptr = NULL;
+ r->stlen = 0;
+ } else {
+ (void) force_number(tmp);
+ if (is_mpg_float(tmp)) {
+ int tval;
+ r = mpg_float();
+ tval = mpfr_set(r->mpg_numbr, tmp->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ } else {
+ r = mpg_integer();
+ mpz_set(r->mpg_i, tmp->mpg_i);
+ }
+ }
+
+ DEREF(tmp);
+ return r;
+}
+
+/* do_mpfr_xor --- perform an ^ operation */
+
+NODE *
+do_mpfr_xor(int nargs)
+{
+ NODE *res;
+
+ if ((res = get_bit_ops("xor")) == NULL) {
+ res = mpg_integer();
+ mpz_xor(res->mpg_i, mpz1, mpz2);
+ }
+ free_bit_ops();
+ return res;
+}
+
+
+static bool firstrand = true;
+static gmp_randstate_t state;
+static mpz_t seed; /* current seed */
+
+/* do_mpfr_rand --- do the rand function */
+
+NODE *
+do_mpfr_rand(int nargs ATTRIBUTE_UNUSED)
+{
+ NODE *res;
+ int tval;
+
+ if (firstrand) {
+#if 0
+ /* Choose the default algorithm */
+ gmp_randinit_default(state);
+#endif
+ /*
+ * Choose a specific (Mersenne Twister) algorithm in case the default
+ * changes in the future.
+ */
+
+ gmp_randinit_mt(state);
+
+ mpz_init(seed);
+ mpz_set_ui(seed, 1);
+ /* seed state */
+ gmp_randseed(state, seed);
+ firstrand = false;
+ }
+ res = mpg_float();
+ tval = mpfr_urandomb(res->mpg_numbr, state);
+ IEEE_FMT(res->mpg_numbr, tval);
+ return res;
+}
+
+
+/* do_mpfr_srand --- seed the random number generator */
+
+NODE *
+do_mpfr_srand(int nargs)
+{
+ NODE *res;
+
+ if (firstrand) {
+#if 0
+ /* Choose the default algorithm */
+ gmp_randinit_default(state);
+#endif
+ /*
+ * Choose a specific algorithm (Mersenne Twister) in case default
+ * changes in the future.
+ */
+
+ gmp_randinit_mt(state);
+
+ mpz_init(seed);
+ mpz_set_ui(seed, 1);
+ /* No need to seed state, will change it below */
+ firstrand = false;
+ }
+
+ res = mpg_integer();
+ mpz_set(res->mpg_i, seed); /* previous seed */
+
+ if (nargs == 0)
+ mpz_set_ui(seed, (unsigned long) time((time_t *) 0));
+ else {
+ NODE *tmp;
+ tmp = POP_SCALAR();
+ if (do_lint && (tmp->flags & (NUMCUR|NUMBER)) == 0)
+ lintwarn(_("srand: received non-numeric argument"));
+ force_number(tmp);
+ if (is_mpg_float(tmp))
+ mpfr_get_z(seed, tmp->mpg_numbr, MPFR_RNDZ);
+ else /* MP integer */
+ mpz_set(seed, tmp->mpg_i);
+ DEREF(tmp);
+ }
+
+ gmp_randseed(state, seed);
+ return res;
+}
+
+/*
+ * mpg_tofloat --- convert an arbitrary-precision integer operand to
+ * a float without loss of precision. It is assumed that the
+ * MPFR variable has already been initialized.
+ */
+
+static inline mpfr_ptr
+mpg_tofloat(mpfr_ptr mf, mpz_ptr mz)
+{
+ size_t prec;
+
+ /*
+ * When implicitely converting a GMP integer operand to a MPFR float, use
+ * a precision sufficiently large to hold the converted value exactly.
+ *
+ * $ ./gawk -M 'BEGIN { print 13 % 2 }'
+ * 1
+ * If the user-specified precision is used to convert the integer 13 to a
+ * float, one will get:
+ * $ ./gawk -M 'BEGIN { PREC=2; print 13 % 2.0 }'
+ * 0
+ */
+
+ prec = mpz_sizeinbase(mz, 2); /* most significant 1 bit position starting at 1 */
+ if (prec > PRECISION_MIN) {
+ prec -= (size_t) mpz_scan1(mz, 0); /* least significant 1 bit index starting at 0 */
+ if (prec > MPFR_PREC_MAX)
+ prec = MPFR_PREC_MAX;
+ if (prec > PRECISION_MIN)
+ mpfr_set_prec(mf, prec);
+ }
+
+ mpfr_set_z(mf, mz, ROUND_MODE);
+ return mf;
+}
+
+
+/* mpg_add --- add arbitrary-precision numbers */
+
+static NODE *
+mpg_add(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)) {
+ r = mpg_integer();
+ mpz_add(r->mpg_i, t1->mpg_i, t2->mpg_i);
+ } else {
+ r = mpg_float();
+ if (is_mpg_integer(t2))
+ tval = mpfr_add_z(r->mpg_numbr, t1->mpg_numbr, t2->mpg_i, ROUND_MODE);
+ else if (is_mpg_integer(t1))
+ tval = mpfr_add_z(r->mpg_numbr, t2->mpg_numbr, t1->mpg_i, ROUND_MODE);
+ else
+ tval = mpfr_add(r->mpg_numbr, t1->mpg_numbr, t2->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+/* mpg_sub --- subtract arbitrary-precision numbers */
+
+static NODE *
+mpg_sub(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)) {
+ r = mpg_integer();
+ mpz_sub(r->mpg_i, t1->mpg_i, t2->mpg_i);
+ } else {
+ r = mpg_float();
+ if (is_mpg_integer(t2))
+ tval = mpfr_sub_z(r->mpg_numbr, t1->mpg_numbr, t2->mpg_i, ROUND_MODE);
+ else if (is_mpg_integer(t1)) {
+#if (!defined(MPFR_VERSION) || (MPFR_VERSION < MPFR_VERSION_NUM(3,1,0)))
+ NODE *tmp = t1;
+ t1 = t2;
+ t2 = tmp;
+ tval = mpfr_sub_z(r->mpg_numbr, t1->mpg_numbr, t2->mpg_i, ROUND_MODE);
+ tval = mpfr_neg(r->mpg_numbr, r->mpg_numbr, ROUND_MODE);
+ t2 = t1;
+ t1 = tmp;
+#else
+ tval = mpfr_z_sub(r->mpg_numbr, t1->mpg_i, t2->mpg_numbr, ROUND_MODE);
+#endif
+ } else
+ tval = mpfr_sub(r->mpg_numbr, t1->mpg_numbr, t2->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+/* mpg_mul --- multiply arbitrary-precision numbers */
+
+static NODE *
+mpg_mul(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)) {
+ r = mpg_integer();
+ mpz_mul(r->mpg_i, t1->mpg_i, t2->mpg_i);
+ } else {
+ r = mpg_float();
+ if (is_mpg_integer(t2))
+ tval = mpfr_mul_z(r->mpg_numbr, t1->mpg_numbr, t2->mpg_i, ROUND_MODE);
+ else if (is_mpg_integer(t1))
+ tval = mpfr_mul_z(r->mpg_numbr, t2->mpg_numbr, t1->mpg_i, ROUND_MODE);
+ else
+ tval = mpfr_mul(r->mpg_numbr, t1->mpg_numbr, t2->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+
+/* mpg_pow --- exponentiation involving arbitrary-precision numbers */
+
+static NODE *
+mpg_pow(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)) {
+ if (mpz_sgn(t2->mpg_i) >= 0 && mpz_fits_ulong_p(t2->mpg_i)) {
+ r = mpg_integer();
+ mpz_pow_ui(r->mpg_i, t1->mpg_i, mpz_get_ui(t2->mpg_i));
+ } else {
+ mpfr_ptr p1, p2;
+ p1 = MP_FLOAT(t1);
+ p2 = MP_FLOAT(t2);
+ r = mpg_float();
+ tval = mpfr_pow(r->mpg_numbr, p1, p2, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ } else {
+ r = mpg_float();
+ if (is_mpg_integer(t2))
+ tval = mpfr_pow_z(r->mpg_numbr, t1->mpg_numbr, t2->mpg_i, ROUND_MODE);
+ else {
+ mpfr_ptr p1;
+ p1 = MP_FLOAT(t1);
+ tval = mpfr_pow(r->mpg_numbr, p1, t2->mpg_numbr, ROUND_MODE);
+ }
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+/* mpg_div --- arbitrary-precision division */
+
+static NODE *
+mpg_div(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)
+ && (mpz_sgn(t2->mpg_i) != 0) /* not dividing by 0 */
+ && mpz_divisible_p(t1->mpg_i, t2->mpg_i)
+ ) {
+ r = mpg_integer();
+ mpz_divexact(r->mpg_i, t1->mpg_i, t2->mpg_i);
+ } else {
+ mpfr_ptr p1, p2;
+ p1 = MP_FLOAT(t1);
+ p2 = MP_FLOAT(t2);
+ r = mpg_float();
+ tval = mpfr_div(r->mpg_numbr, p1, p2, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+/* mpg_mod --- modulus operation with arbitrary-precision numbers */
+
+static NODE *
+mpg_mod(NODE *t1, NODE *t2)
+{
+ NODE *r;
+ int tval;
+
+ if (is_mpg_integer(t1) && is_mpg_integer(t2)) {
+ r = mpg_integer();
+ mpz_mod(r->mpg_i, t1->mpg_i, t2->mpg_i);
+ } else {
+ mpfr_ptr p1, p2;
+ p1 = MP_FLOAT(t1);
+ p2 = MP_FLOAT(t2);
+ r = mpg_float();
+ tval = mpfr_fmod(r->mpg_numbr, p1, p2, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ return r;
+}
+
+/*
+ * mpg_interpret --- pre-exec hook in the interpreter. Handles
+ * arithmetic operations with MPFR/GMP numbers.
+ */
+
+static int
+mpg_interpret(INSTRUCTION **cp)
+{
+ INSTRUCTION *pc = *cp; /* current instruction */
+ OPCODE op; /* current opcode */
+ NODE *r = NULL;
+ NODE *t1, *t2;
+ NODE **lhs;
+ int tval; /* the ternary value returned by a MPFR function */
+
+ switch ((op = pc->opcode)) {
+ case Op_plus_i:
+ t2 = force_number(pc->memory);
+ goto plus;
+ case Op_plus:
+ t2 = POP_NUMBER();
+plus:
+ t1 = TOP_NUMBER();
+ r = mpg_add(t1, t2);
+ DEREF(t1);
+ if (op == Op_plus)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_minus_i:
+ t2 = force_number(pc->memory);
+ goto minus;
+ case Op_minus:
+ t2 = POP_NUMBER();
+minus:
+ t1 = TOP_NUMBER();
+ r = mpg_sub(t1, t2);
+ DEREF(t1);
+ if (op == Op_minus)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_times_i:
+ t2 = force_number(pc->memory);
+ goto times;
+ case Op_times:
+ t2 = POP_NUMBER();
+times:
+ t1 = TOP_NUMBER();
+ r = mpg_mul(t1, t2);
+ DEREF(t1);
+ if (op == Op_times)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_exp_i:
+ t2 = force_number(pc->memory);
+ goto exp;
+ case Op_exp:
+ t2 = POP_NUMBER();
+exp:
+ t1 = TOP_NUMBER();
+ r = mpg_pow(t1, t2);
+ DEREF(t1);
+ if (op == Op_exp)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_quotient_i:
+ t2 = force_number(pc->memory);
+ goto quotient;
+ case Op_quotient:
+ t2 = POP_NUMBER();
+quotient:
+ t1 = TOP_NUMBER();
+ r = mpg_div(t1, t2);
+ DEREF(t1);
+ if (op == Op_quotient)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_mod_i:
+ t2 = force_number(pc->memory);
+ goto mod;
+ case Op_mod:
+ t2 = POP_NUMBER();
+mod:
+ t1 = TOP_NUMBER();
+ r = mpg_mod(t1, t2);
+ DEREF(t1);
+ if (op == Op_mod)
+ DEREF(t2);
+ REPLACE(r);
+ break;
+
+ case Op_preincrement:
+ case Op_predecrement:
+ lhs = TOP_ADDRESS();
+ t1 = *lhs;
+ force_number(t1);
+
+ if (is_mpg_integer(t1)) {
+ if (t1->valref == 1 && t1->flags == (MALLOC|MPZN|NUMCUR|NUMBER))
+ /* Efficiency hack. Big speed-up (> 30%) in a tight loop */
+ r = t1;
+ else
+ r = *lhs = mpg_integer();
+ if (op == Op_preincrement)
+ mpz_add_ui(r->mpg_i, t1->mpg_i, 1);
+ else
+ mpz_sub_ui(r->mpg_i, t1->mpg_i, 1);
+ } else {
+
+ /*
+ * An optimization like the one above is not going to work
+ * for a floating-point number. With it,
+ * gawk -M 'BEGIN { PREC=53; i=2^53+0.0; PREC=113; ++i; print i}'
+ * will output 2^53 instead of 2^53+1.
+ */
+
+ r = *lhs = mpg_float();
+ tval = mpfr_add_si(r->mpg_numbr, t1->mpg_numbr,
+ op == Op_preincrement ? 1 : -1,
+ ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ }
+ if (r != t1)
+ unref(t1);
+ UPREF(r);
+ REPLACE(r);
+ break;
+
+ case Op_postincrement:
+ case Op_postdecrement:
+ lhs = TOP_ADDRESS();
+ t1 = *lhs;
+ force_number(t1);
+
+ if (is_mpg_integer(t1)) {
+ r = mpg_integer();
+ mpz_set(r->mpg_i, t1->mpg_i);
+ if (t1->valref == 1 && t1->flags == (MALLOC|MPZN|NUMCUR|NUMBER))
+ /* Efficiency hack. Big speed-up (> 30%) in a tight loop */
+ t2 = t1;
+ else
+ t2 = *lhs = mpg_integer();
+ if (op == Op_postincrement)
+ mpz_add_ui(t2->mpg_i, t1->mpg_i, 1);
+ else
+ mpz_sub_ui(t2->mpg_i, t1->mpg_i, 1);
+ } else {
+ r = mpg_float();
+ tval = mpfr_set(r->mpg_numbr, t1->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ t2 = *lhs = mpg_float();
+ tval = mpfr_add_si(t2->mpg_numbr, t1->mpg_numbr,
+ op == Op_postincrement ? 1 : -1,
+ ROUND_MODE);
+ IEEE_FMT(t2->mpg_numbr, tval);
+ }
+ if (t2 != t1)
+ unref(t1);
+ REPLACE(r);
+ break;
+
+ case Op_unary_minus:
+ t1 = TOP_NUMBER();
+ if (is_mpg_float(t1)) {
+ r = mpg_float();
+ tval = mpfr_neg(r->mpg_numbr, t1->mpg_numbr, ROUND_MODE);
+ IEEE_FMT(r->mpg_numbr, tval);
+ } else {
+ r = mpg_integer();
+ mpz_neg(r->mpg_i, t1->mpg_i);
+ }
+ DEREF(t1);
+ REPLACE(r);
+ break;
+
+ case Op_assign_plus:
+ case Op_assign_minus:
+ case Op_assign_times:
+ case Op_assign_quotient:
+ case Op_assign_mod:
+ case Op_assign_exp:
+ lhs = POP_ADDRESS();
+ t1 = *lhs;
+ force_number(t1);
+ t2 = TOP_NUMBER();
+
+ switch (op) {
+ case Op_assign_plus:
+ r = mpg_add(t1, t2);
+ break;
+ case Op_assign_minus:
+ r = mpg_sub(t1, t2);
+ break;
+ case Op_assign_times:
+ r = mpg_mul(t1, t2);
+ break;
+ case Op_assign_quotient:
+ r = mpg_div(t1, t2);
+ break;
+ case Op_assign_mod:
+ r = mpg_mod(t1, t2);
+ break;
+ case Op_assign_exp:
+ r = mpg_pow(t1, t2);
+ break;
+ default:
+ cant_happen();
+ }
+
+ DEREF(t2);
+ unref(*lhs);
+ *lhs = r;
+ UPREF(r);
+ REPLACE(r);
+ break;
+
+ default:
+ return true; /* unhandled */
+ }
+
+ *cp = pc->nexti; /* next instruction to execute */
+ return false;
+}
+
+
+/* mpg_fmt --- output formatted string with special MPFR/GMP conversion specifiers */
+
+const char *
+mpg_fmt(const char *mesg, ...)
+{
+ static char *tmp = NULL;
+ int ret;
+ va_list args;
+
+ if (tmp != NULL) {
+ mpfr_free_str(tmp);
+ tmp = NULL;
+ }
+ va_start(args, mesg);
+ ret = mpfr_vasprintf(& tmp, mesg, args);
+ va_end(args);
+ if (ret >= 0 && tmp != NULL)
+ return tmp;
+ return mesg;
+}
+
+/* mpfr_unset --- clear out the MPFR values */
+
+void
+mpfr_unset(NODE *n)
+{
+ if (is_mpg_float(n))
+ mpfr_clear(n->mpg_numbr);
+ else if (is_mpg_integer(n))
+ mpz_clear(n->mpg_i);
+}
+
+#else
+
+void
+set_PREC()
+{
+ /* dummy function */
+}
+
+void
+set_ROUNDMODE()
+{
+ /* dummy function */
+}
+
+void
+mpfr_unset(NODE *n)
+{
+ /* dummy function */
+}
+#endif