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