/* * 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