summaryrefslogtreecommitdiff
path: root/get_str.c
blob: 6db7e3759f8b3710ed1597bc15d63989b073ae83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "gmp.h"
#include "gmp-impl.h"
#include "longlong.h"
#include "mpfr.h"

/*
  Convert op to a string in base 'base' with 'n' digits and writes the 
  mantissa in 'str', the exponent in 'expptr'.
  The result is rounded wrt 'rnd_mode'.

  For op = 3.1416 we get str = "31416" and expptr=1.
 */

char *mpfr_get_str(char *str, mp_exp_t *expptr, int base, size_t n,
		  mpfr_srcptr op, unsigned char rnd_mode)
{
  double d; long e, q, neg, p, err, prec, sh; mpfr_t a, b; mpz_t bz;
  char *str0; unsigned char rnd1; int f, pow2;

  if (base<2 || 36<base) {
    fprintf(stderr, "Error: too small or too large base in mpfr_get_str: %d\n",
	    base);
    exit(1);
  }
  count_leading_zeros(pow2, (mp_limb_t)base); 
  pow2 = mp_bits_per_limb - pow2 - 1;
  if (base != (1<<pow2)) pow2=0; 
  /* if pow2 <> 0, then base = 2^pow2 */

  /* first determines the exponent */
  e = EXP(op); 
  d = fabs(mpfr_get_d2(op, 0));
  /* the absolute value of op is between 1/2*2^e and 2^e */
  /* the output exponent f is such that base^(f-1) <= |op| < base^f
     i.e. f = 1 + floor(log(|op|)/log(base))
     = 1 + floor((log(|m|)+e*log(2))/log(base)) */
  f = 1 + (int) floor((log(d)+((double)e)*log(2.0))/log((double)base));

  if (n==0)
    n = (int) ceil((double)PREC(op)*log(2.0)/log((double)base)+
		   log(4.0*fabs((double)((f==0) ? 1 : f)))/log(2.0));
  /* now the first n digits of the mantissa are obtained from
     rnd(op*base^(n-f)) */
  prec = (long) ceil((double)n*log((double)base)/log(2.0));
  err = 5;
  q = prec+err;
  /* one has to use at least q bits */
  q = ((q-1)/mp_bits_per_limb)*mp_bits_per_limb;
  mpfr_init(a); mpfr_init(b);
  p = n-f; if ((neg=(p<0))) p=-p;

  rnd1 = rnd_mode;
  if (neg) {
    /* if neg we divide by base^p so we have to invert the rounding mode */
    switch (rnd1) {
    case GMP_RNDN: rnd1=GMP_RNDN; break;
    case GMP_RNDZ: rnd1=GMP_RNDU; break;
    case GMP_RNDU: rnd1=GMP_RNDZ; break;
    case GMP_RNDD: rnd1=GMP_RNDZ; break;
    }
  }
  do {
    q += mp_bits_per_limb;
    if (pow2) {
      if (neg) mpfr_div_2exp(b, op, pow2*p, rnd_mode);
      else mpfr_mul_2exp(b, op, pow2*p, rnd_mode);
    } 
    else {
       /* compute base^p with q bits and rounding towards zero */
       mpfr_set_prec(b, q);
       if (p==0) mpfr_set(b, op, rnd_mode);
       else {
	 mpfr_set_prec(a, q); 
	 mpfr_ui_pow_ui(a, base, p, rnd1);
	 /* now a is an approximation by default of base^p */
	 if (neg) mpfr_div(b, op, a, rnd_mode);
	 else mpfr_mul(b, op, a, rnd_mode);
       }
    }
    if (SIGN(op)<0) CHANGE_SIGN(b);
    if (q>2*prec+mp_bits_per_limb) {
      fprintf(stderr, "no convergence in mpfr_get_str\n"); exit(1);
    }
  } while (pow2==0 && mpfr_can_round(b, q-err, rnd_mode, rnd_mode, prec)==0);
  if (SIGN(op)<0)
    switch (rnd_mode) {
    case GMP_RNDU: rnd_mode=GMP_RNDZ; break;
    case GMP_RNDD: rnd_mode=GMP_RNDU; break;
  }

  prec=EXP(b); 
  mpfr_round(b, rnd_mode, prec);
  prec=EXP(b); /* may have changed due to rounding */

  /* now the mantissa is the integer part of b */
  mpz_init(bz); q=1+(prec-1)/mp_bits_per_limb;
  _mpz_realloc(bz, q);
  sh = prec%mp_bits_per_limb;
  if (sh) mpn_rshift(PTR(bz), MANT(b), q, mp_bits_per_limb-sh);
  else MPN_COPY(PTR(bz), MANT(b), q);
  bz->_mp_size=q;

  /* computes the number of characters needed */
  q = ((SIGN(op)<0) ? 1 : 0) + n + 1;
  if (str==NULL) str0=str=malloc(q); 
  if (SIGN(op)<0) *str++='-';
  mpz_get_str(str, base, bz); /* n digits of mantissa */
  if (strlen(str)==n+1) f++; /* possible due to rounding */
  *expptr = f;
  mpfr_clear(a); mpfr_clear(b); mpz_clear(bz);
  return str0;
}