/* Copyright (C) 2015 Fredrik Johansson This file is part of Arb. Arb is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See . */ #include #include #include "arb.h" #define RADIUS_DIGITS 3 char * _arb_condense_digits(char * s, slong n) { slong i, j, run, out; char * res; res = flint_malloc(strlen(s) + 128); /* space for some growth */ out = 0; for (i = 0; s[i] != '\0'; ) { if (isdigit(s[i])) { run = 0; for (j = 0; isdigit(s[i + j]); j++) run++; if (run > 3 * n) { for (j = 0; j < n; j++) { res[out] = s[i + j]; out++; } out += flint_sprintf(res + out, "{...%wd digits...}", run - 2 * n); for (j = run - n; j < run; j++) { res[out] = s[i + j]; out++; } } else { for (j = 0; j < run; j++) { res[out] = s[i + j]; out++; } } i += run; } else { res[out] = s[i]; i++; out++; } } res[out] = '\0'; res = flint_realloc(res, strlen(res) + 1); flint_free(s); return res; } /* Format (digits=d, exponent=e) as floating-point or fixed-point. Reallocates the input and mutates the exponent. */ void _arb_digits_as_float_str(char ** d, fmpz_t e, slong minfix, slong maxfix) { slong i, n, alloc, dotpos; /* do nothing with 0 or something non-numerical */ if (!((*d)[0] >= '1' && (*d)[0] <= '9')) return; n = strlen(*d); fmpz_add_ui(e, e, n - 1); /* fixed-point or integer format */ /* we require e < n - 1; otherwise we would have to insert trailing zeros [todo: could allow e < n, if printing integers without radix point] */ if (fmpz_cmp_si(e, minfix) >= 0 && fmpz_cmp_si(e, maxfix) <= 0 && fmpz_cmp_si(e, n - 1) < 0) { slong exp = *e; /* 0.000xxx */ if (exp < 0) { /* 0. + (-1-exp) zeros + digits + null terminator */ alloc = 2 + (-1-exp) + n + 1; *d = flint_realloc(*d, alloc); /* copy in reverse order, including null terminator */ for (i = n; i >= 0; i--) (*d)[2 + (-1-exp) + i] = (*d)[i]; for (i = 0; i < 2 + (-1-exp); i++) (*d)[i] = (i == 1) ? '.' : '0'; } else /* xxx.yyy --- must have dotpos < n - 1 */ { dotpos = exp + 1; alloc = n + 2; /* space for . and null terminator */ (*d) = flint_realloc(*d, alloc); /* copy fractional part in reverse order, including null */ for (i = n; i >= dotpos; i--) (*d)[i + 1] = (*d)[i]; (*d)[dotpos] = '.'; } } else { /* format as xe+zzz or x.yyye+zzz */ alloc = n + 1 + 2 + fmpz_sizeinbase(e, 10) + 1; *d = flint_realloc(*d, alloc); /* insert . */ if (n > 1) { /* copy fractional part in reverse order */ for (i = n; i >= 1; i--) (*d)[i + 1] = (*d)[i]; (*d)[1] = '.'; } (*d)[n + (n > 1)] = 'e'; if (fmpz_sgn(e) >= 0) { (*d)[n + (n > 1) + 1] = '+'; } else { (*d)[n + (n > 1) + 1] = '-'; fmpz_neg(e, e); } fmpz_get_str((*d) + n + (n > 1) + 2, 10, e); /* writes null byte */ } } /* Rounds a string of decimal digits (null-terminated). to length at most n. The rounding mode can be ARF_RND_DOWN, ARF_RND_UP or ARF_RND_NEAR. The string is overwritten in-place, truncating it as necessary. The input should not have a leading sign or leading zero digits, but can have trailing zero digits. Computes shift and error such that int(input) = int(output) * 10^shift + error exactly. */ void _arb_digits_round_inplace(char * s, flint_bitcnt_t * shift, fmpz_t error, slong n, arf_rnd_t rnd) { slong i, m; int up; if (n < 1) { flint_printf("_arb_digits_round_inplace: require n >= 1\n"); flint_abort(); } m = strlen(s); if (m <= n) { *shift = 0; fmpz_zero(error); return; } /* always round down */ if (rnd == ARF_RND_DOWN) { up = 0; } else if (rnd == ARF_RND_UP) /* round up if tail is nonzero */ { up = 0; for (i = n; i < m; i++) { if (s[i] != '0') { up = 1; break; } } } else /* round to nearest (up on tie -- todo: round-to-even?) */ { up = (s[n] >= '5' && s[n] <= '9'); } if (!up) { /* simply truncate */ fmpz_set_str(error, s + n, 10); s[n] = '\0'; *shift = m - n; } else { int digit, borrow, carry; /* error = 10^(m-n) - s[n:], where s[n:] is nonzero */ /* i.e. 10s complement the truncated digits */ borrow = 0; for (i = m - 1; i >= n; i--) { digit = 10 - (s[i] - '0') - borrow; if (digit == 10) { digit = 0; borrow = 0; } else { borrow = 1; } s[i] = digit + '0'; } if (!borrow) { flint_printf("expected borrow!\n"); flint_abort(); } fmpz_set_str(error, s + n, 10); fmpz_neg(error, error); /* add 1 ulp to the leading digits */ carry = 1; for (i = n - 1; i >= 0; i--) { digit = (s[i] - '0') + carry; if (digit > 9) { digit = 0; carry = 1; } else { carry = 0; } s[i] = digit + '0'; } /* carry-out -- only possible if we started with all 9s, so now the rest will be 0s which we don't have to shift explicitly */ if (carry) { s[0] = '1'; *shift = m - n + 1; } else { *shift = m - n; } s[n] = '\0'; /* truncate */ } } void arb_get_str_parts(int * negative, char **mid_digits, fmpz_t mid_exp, char **rad_digits, fmpz_t rad_exp, const arb_t x, slong n, int more) { fmpz_t mid, rad, exp, err; slong good; flint_bitcnt_t shift; if (!arb_is_finite(x)) { *negative = 0; fmpz_zero(mid_exp); *mid_digits = flint_malloc(4); if (arf_is_nan(arb_midref(x))) strcpy(*mid_digits, "nan"); else strcpy(*mid_digits, "0"); fmpz_zero(rad_exp); *rad_digits = flint_malloc(4); strcpy(*rad_digits, "inf"); return; } fmpz_init(mid); fmpz_init(rad); fmpz_init(exp); fmpz_init(err); /* heuristic part */ if (!more) { good = arb_rel_accuracy_bits(x) * 0.30102999566398119521 + 2; n = FLINT_MIN(n, good); } arb_get_fmpz_mid_rad_10exp(mid, rad, exp, x, FLINT_MAX(n, 1)); *negative = arf_sgn(arb_midref(x)) < 0; fmpz_abs(mid, mid); *mid_digits = fmpz_get_str(NULL, 10, mid); *rad_digits = NULL; /* Truncate further so that 1 ulp error can be guaranteed (rigorous part) Note: mid cannot be zero here if n >= 1 and rad != 0. */ if (n >= 1 && !(more || fmpz_is_zero(rad))) { slong lenmid, lenrad, rem; *rad_digits = fmpz_get_str(NULL, 10, rad); lenmid = strlen(*mid_digits); lenrad = strlen(*rad_digits); if (lenmid > lenrad) { /* we will truncate at n or n-1 */ good = lenmid - lenrad; /* rounding to nearest can add at most 0.5 ulp */ /* look at first omitted digit */ rem = ((*mid_digits)[good]) - '0'; if (rem < 5) rem = rem + 1; else rem = 10 - rem; /* and include the leading digit of the radius */ rem = rem + ((*rad_digits)[0] - '0') + 1; /* if error is <= 1.0 ulp, we get to keep the extra digit */ if (rem > 10) good -= 1; n = FLINT_MIN(n, good); } else { n = 0; } /* todo: avoid recomputing? */ flint_free(*rad_digits); } /* no accurate digits -- output 0 +/- rad */ if (n < 1) { fmpz_add(rad, rad, mid); fmpz_zero(mid); strcpy(*mid_digits, "0"); /* must have space already! */ } else { _arb_digits_round_inplace(*mid_digits, &shift, err, n, ARF_RND_NEAR); fmpz_add_ui(mid_exp, exp, shift); fmpz_abs(err, err); fmpz_add(rad, rad, err); } /* write radius */ if (fmpz_is_zero(rad)) { *rad_digits = fmpz_get_str(NULL, 10, rad); fmpz_zero(rad_exp); } else { *rad_digits = fmpz_get_str(NULL, 10, rad); _arb_digits_round_inplace(*rad_digits, &shift, err, RADIUS_DIGITS, ARF_RND_UP); fmpz_add_ui(rad_exp, exp, shift); } fmpz_clear(mid); fmpz_clear(rad); fmpz_clear(exp); fmpz_clear(err); } char * arb_get_str(const arb_t x, slong n, ulong flags) { char * res; char * mid_digits; char * rad_digits; int negative, more, skip_rad, skip_mid; fmpz_t mid_exp; fmpz_t rad_exp; slong condense; if (arb_is_zero(x)) { res = flint_malloc(2); strcpy(res, "0"); return res; } more = flags & ARB_STR_MORE; condense = flags / ARB_STR_CONDENSE; if (!arb_is_finite(x)) { res = flint_malloc(10); if (arf_is_nan(arb_midref(x))) strcpy(res, "nan"); else strcpy(res, "[+/- inf]"); return res; } fmpz_init(mid_exp); fmpz_init(rad_exp); arb_get_str_parts(&negative, &mid_digits, mid_exp, &rad_digits, rad_exp, x, n, more); if ((flags & ARB_STR_NO_RADIUS) && mid_digits[0] == '0') { fmpz_add_ui(rad_exp, rad_exp, strlen(rad_digits)); res = flint_malloc(fmpz_sizeinbase(rad_exp, 10) + 4); res[0] = '0'; res[1] = 'e'; if (fmpz_sgn(rad_exp) >= 0) { res[2] = '+'; fmpz_get_str(res + 3, 10, rad_exp); } else { fmpz_get_str(res + 2, 10, rad_exp); } } else { skip_mid = mid_digits[0] == '0'; skip_rad = (rad_digits[0] == '0') || ((flags & ARB_STR_NO_RADIUS) && !skip_mid); _arb_digits_as_float_str(&mid_digits, mid_exp, -4, FLINT_MAX(6, n - 1)); _arb_digits_as_float_str(&rad_digits, rad_exp, -2, 2); if (skip_rad) { res = flint_malloc(strlen(mid_digits) + 2); if (negative) strcpy(res, "-"); else strcpy(res, ""); strcat(res, mid_digits); } else if (skip_mid) { res = flint_malloc(strlen(rad_digits) + 7); strcpy(res, "[+/- "); strcat(res, rad_digits); strcat(res, "]"); } else { res = flint_malloc(strlen(mid_digits) + strlen(rad_digits) + 9); strcpy(res, "["); if (negative) strcat(res, "-"); strcat(res, mid_digits); strcat(res, " +/- "); strcat(res, rad_digits); strcat(res, "]"); } } if (condense) res = _arb_condense_digits(res, condense); flint_free(mid_digits); flint_free(rad_digits); fmpz_clear(mid_exp); fmpz_clear(rad_exp); return res; }