1 /* __gmp_doprnt_mpf -- mpf formatted output.
2 
3    THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY.  THEY'RE ALMOST
4    CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN
5    FUTURE GNU MP RELEASES.
6 
7 Copyright 2001, 2002, 2011 Free Software Foundation, Inc.
8 
9 This file is part of the GNU MP Library.
10 
11 The GNU MP Library is free software; you can redistribute it and/or modify
12 it under the terms of either:
13 
14   * the GNU Lesser General Public License as published by the Free
15     Software Foundation; either version 3 of the License, or (at your
16     option) any later version.
17 
18 or
19 
20   * the GNU General Public License as published by the Free Software
21     Foundation; either version 2 of the License, or (at your option) any
22     later version.
23 
24 or both in parallel, as here.
25 
26 The GNU MP Library is distributed in the hope that it will be useful, but
27 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
28 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
29 for more details.
30 
31 You should have received copies of the GNU General Public License and the
32 GNU Lesser General Public License along with the GNU MP Library.  If not,
33 see https://www.gnu.org/licenses/.  */
34 
35 #include <stdarg.h>    /* for va_list and hence doprnt_funs_t */
36 #include <ctype.h>
37 #include <string.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 
41 #include "gmp.h"
42 #include "gmp-impl.h"
43 #include "longlong.h"
44 
45 
46 /* change this to "#define TRACE(x) x" for diagnostics */
47 #define TRACE(x)
48 
49 
50 /* The separate of __gmp_doprnt_float_digits and __gmp_doprnt_float is so
51    some C++ can do the mpf_get_str and release it in case of an exception */
52 
53 #define DIGIT_VALUE(c)                  \
54   (isdigit (c)   ? (c) - '0'            \
55    : islower (c) ? (c) - 'a' + 10       \
56    :               (c) - 'A' + 10)
57 
58 int
__gmp_doprnt_mpf(const struct doprnt_funs_t * funs,void * data,const struct doprnt_params_t * p,const char * point,mpf_srcptr f)59 __gmp_doprnt_mpf (const struct doprnt_funs_t *funs,
60 		  void *data,
61 		  const struct doprnt_params_t *p,
62 		  const char *point,
63 		  mpf_srcptr f)
64 {
65   int         prec, ndigits, free_size, len, newlen, justify, justlen, explen;
66   int         showbaselen, sign, signlen, intlen, intzeros, pointlen;
67   int         fraczeros, fraclen, preczeros;
68   char        *s, *free_ptr;
69   mp_exp_t    exp;
70   char        exponent[GMP_LIMB_BITS + 10];
71   const char  *showbase;
72   int         retval = 0;
73 
74   TRACE (printf ("__gmp_doprnt_float\n");
75 	 printf ("  conv=%d prec=%d\n", p->conv, p->prec));
76 
77   prec = p->prec;
78   if (prec <= -1)
79     {
80       /* all digits */
81       ndigits = 0;
82 
83       /* arrange the fixed/scientific decision on a "prec" implied by how
84 	 many significant digits there are */
85       if (p->conv == DOPRNT_CONV_GENERAL)
86 	MPF_SIGNIFICANT_DIGITS (prec, PREC(f), ABS(p->base));
87     }
88   else
89     {
90       switch (p->conv) {
91       case DOPRNT_CONV_FIXED:
92 	/* Precision is digits after the radix point.  Try not to generate
93 	   too many more than will actually be required.  If f>=1 then
94 	   overestimate the integer part, and add prec.  If f<1 then
95 	   underestimate the zeros between the radix point and the first
96 	   digit and subtract that from prec.  In either case add 2 so the
97 	   round to nearest can be applied accurately.  Finally, we add 1 to
98 	   handle the case of 1-eps where EXP(f) = 0 but mpf_get_str returns
99 	   exp as 1.  */
100 	ndigits = prec + 2 + 1
101 	  + EXP(f) * (mp_bases[ABS(p->base)].chars_per_limb + (EXP(f)>=0));
102 	ndigits = MAX (ndigits, 1);
103 	break;
104 
105       case DOPRNT_CONV_SCIENTIFIC:
106 	/* precision is digits after the radix point, and there's one digit
107 	   before */
108 	ndigits = prec + 1;
109 	break;
110 
111       default:
112 	ASSERT (0);
113 	/*FALLTHRU*/
114 
115       case DOPRNT_CONV_GENERAL:
116 	/* precision is total digits, but be sure to ask mpf_get_str for at
117 	   least 1, not 0 */
118 	ndigits = MAX (prec, 1);
119 	break;
120       }
121     }
122   TRACE (printf ("  ndigits %d\n", ndigits));
123 
124   s = mpf_get_str (NULL, &exp, p->base, ndigits, f);
125   len = strlen (s);
126   free_ptr = s;
127   free_size = len + 1;
128   TRACE (printf ("  s   %s\n", s);
129 	 printf ("  exp %ld\n", exp);
130 	 printf ("  len %d\n", len));
131 
132   /* For fixed mode check the ndigits formed above was in fact enough for
133      the integer part plus p->prec after the radix point. */
134   ASSERT ((p->conv == DOPRNT_CONV_FIXED && p->prec > -1)
135 	  ? ndigits >= MAX (1, exp + p->prec + 2) : 1);
136 
137   sign = p->sign;
138   if (s[0] == '-')
139     {
140       sign = s[0];
141       s++, len--;
142     }
143   signlen = (sign != '\0');
144   TRACE (printf ("  sign %c  signlen %d\n", sign, signlen));
145 
146   switch (p->conv) {
147   case DOPRNT_CONV_FIXED:
148     if (prec <= -1)
149       prec = MAX (0, len-exp);   /* retain all digits */
150 
151     /* Truncate if necessary so fraction will be at most prec digits. */
152     ASSERT (prec >= 0);
153     newlen = exp + prec;
154     if (newlen < 0)
155       {
156 	/* first non-zero digit is below target prec, and at least one zero
157 	   digit in between, so print zero */
158 	len = 0;
159 	exp = 0;
160       }
161     else if (len <= newlen)
162       {
163 	/* already got few enough digits */
164       }
165     else
166       {
167 	/* discard excess digits and round to nearest */
168 
169 	const char  *num_to_text = (p->base >= 0
170 				    ? "0123456789abcdefghijklmnopqrstuvwxyz"
171 				    : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
172 	int  base = ABS(p->base);
173 	int  n;
174 
175 	ASSERT (base <= 36);
176 
177 	len = newlen;
178 	n = DIGIT_VALUE (s[len]);
179 	TRACE (printf ("  rounding with %d\n", n));
180 	if (n >= (base + 1) / 2)
181 	  {
182 	    /* propagate a carry */
183 	    for (;;)
184 	      {
185 		if (len == 0)
186 		  {
187 		    s[0] = '1';
188 		    len = 1;
189 		    exp++;
190 		    break;
191 		  }
192 		n = DIGIT_VALUE (s[len-1]);
193 		ASSERT (n >= 0 && n < base);
194 		n++;
195 		if (n != base)
196 		  {
197 		    TRACE (printf ("  storing now %d\n", n));
198 		    s[len-1] = num_to_text[n];
199 		    break;
200 		  }
201 		len--;
202 	      }
203 	  }
204 	else
205 	  {
206 	    /* truncate only, strip any trailing zeros now exposed */
207 	    while (len > 0 && s[len-1] == '0')
208 	      len--;
209 	  }
210 
211 	/* Can have newlen==0, in which case the truncate was just to check
212 	   for a carry turning it into "1".  If we're left with len==0 then
213 	   adjust exp to match.  */
214 	if (len == 0)
215 	  exp = 0;
216       }
217 
218   fixed:
219     ASSERT (len == 0 ? exp == 0 : 1);
220     if (exp <= 0)
221       {
222 	TRACE (printf ("  fixed 0.000sss\n"));
223 	intlen = 0;
224 	intzeros = 1;
225 	fraczeros = -exp;
226 	fraclen = len;
227       }
228     else
229       {
230 	TRACE (printf ("  fixed sss.sss or sss000\n"));
231 	intlen = MIN (len, exp);
232 	intzeros = exp - intlen;
233 	fraczeros = 0;
234 	fraclen = len - intlen;
235       }
236     explen = 0;
237     break;
238 
239   case DOPRNT_CONV_SCIENTIFIC:
240     {
241       long int expval;
242       char  expsign;
243 
244       if (prec <= -1)
245 	prec = MAX (0, len-1);   /* retain all digits */
246 
247     scientific:
248       TRACE (printf ("  scientific s.sss\n"));
249 
250       intlen = MIN (1, len);
251       intzeros = (intlen == 0 ? 1 : 0);
252       fraczeros = 0;
253       fraclen = len - intlen;
254 
255       expval = (exp-intlen);
256       if (p->exptimes4)
257 	expval <<= 2;
258 
259       /* Split out the sign since %o or %x in expfmt give negatives as twos
260 	 complement, not with a sign. */
261       expsign = (expval >= 0 ? '+' : '-');
262       expval = ABS (expval);
263 
264 #if HAVE_VSNPRINTF
265       explen = snprintf (exponent, sizeof(exponent),
266 			 p->expfmt, expsign, expval);
267       /* test for < sizeof-1 since a glibc 2.0.x return of sizeof-1 might
268 	 mean truncation */
269       ASSERT (explen >= 0 && explen < sizeof(exponent)-1);
270 #else
271       sprintf (exponent, p->expfmt, expsign, expval);
272       explen = strlen (exponent);
273       ASSERT (explen < sizeof(exponent));
274 #endif
275       TRACE (printf ("  expfmt %s gives %s\n", p->expfmt, exponent));
276     }
277     break;
278 
279   default:
280     ASSERT (0);
281     /*FALLTHRU*/  /* to stop variables looking uninitialized */
282 
283   case DOPRNT_CONV_GENERAL:
284     /* The exponent for "scientific" will be exp-1, choose scientific if
285        this is < -4 or >= prec (and minimum 1 for prec).  For f==0 will have
286        exp==0 and get the desired "fixed".  This rule follows glibc.  For
287        fixed there's no need to truncate, the desired ndigits will already
288        be as required.  */
289     if (exp-1 < -4 || exp-1 >= MAX (1, prec))
290       goto scientific;
291     else
292       goto fixed;
293   }
294 
295   TRACE (printf ("  intlen %d intzeros %d fraczeros %d fraclen %d\n",
296 		 intlen, intzeros, fraczeros, fraclen));
297   ASSERT (p->prec <= -1
298 	  ? intlen + fraclen == strlen (s)
299 	  : intlen + fraclen <= strlen (s));
300 
301   if (p->showtrailing)
302     {
303       /* Pad to requested precision with trailing zeros, for general this is
304 	 all digits, for fixed and scientific just the fraction.  */
305       preczeros = prec - (fraczeros + fraclen
306 			  + (p->conv == DOPRNT_CONV_GENERAL
307 			     ? intlen + intzeros : 0));
308       preczeros = MAX (0, preczeros);
309     }
310   else
311     preczeros = 0;
312   TRACE (printf ("  prec=%d showtrailing=%d, pad with preczeros %d\n",
313 		 prec, p->showtrailing, preczeros));
314 
315   /* radix point if needed, or if forced */
316   pointlen = ((fraczeros + fraclen + preczeros) != 0 || p->showpoint != 0)
317     ? strlen (point) : 0;
318   TRACE (printf ("  point |%s|  pointlen %d\n", point, pointlen));
319 
320   /* Notice the test for a non-zero value is done after any truncation for
321      DOPRNT_CONV_FIXED. */
322   showbase = NULL;
323   showbaselen = 0;
324   switch (p->showbase) {
325   default:
326     ASSERT (0);
327     /*FALLTHRU*/
328   case DOPRNT_SHOWBASE_NO:
329     break;
330   case DOPRNT_SHOWBASE_NONZERO:
331     if (intlen == 0 && fraclen == 0)
332       break;
333     /*FALLTHRU*/
334   case DOPRNT_SHOWBASE_YES:
335     switch (p->base) {
336     case 16:  showbase = "0x"; showbaselen = 2; break;
337     case -16: showbase = "0X"; showbaselen = 2; break;
338     case 8:   showbase = "0";  showbaselen = 1; break;
339     }
340     break;
341   }
342   TRACE (printf ("  showbase %s showbaselen %d\n",
343 		 showbase == NULL ? "" : showbase, showbaselen));
344 
345   /* left over field width */
346   justlen = p->width - (signlen + showbaselen + intlen + intzeros + pointlen
347 			+ fraczeros + fraclen + preczeros + explen);
348   TRACE (printf ("  justlen %d fill 0x%X\n", justlen, p->fill));
349 
350   justify = p->justify;
351   if (justlen <= 0) /* no justifying if exceed width */
352     justify = DOPRNT_JUSTIFY_NONE;
353 
354   TRACE (printf ("  justify type %d  intlen %d pointlen %d fraclen %d\n",
355 		 justify, intlen, pointlen, fraclen));
356 
357   if (justify == DOPRNT_JUSTIFY_RIGHT)         /* pad for right */
358     DOPRNT_REPS (p->fill, justlen);
359 
360   if (signlen)                                 /* sign */
361     DOPRNT_REPS (sign, 1);
362 
363   DOPRNT_MEMORY_MAYBE (showbase, showbaselen); /* base */
364 
365   if (justify == DOPRNT_JUSTIFY_INTERNAL)      /* pad for internal */
366     DOPRNT_REPS (p->fill, justlen);
367 
368   DOPRNT_MEMORY (s, intlen);                   /* integer */
369   DOPRNT_REPS_MAYBE ('0', intzeros);
370 
371   DOPRNT_MEMORY_MAYBE (point, pointlen);       /* point */
372 
373   DOPRNT_REPS_MAYBE ('0', fraczeros);          /* frac */
374   DOPRNT_MEMORY_MAYBE (s+intlen, fraclen);
375 
376   DOPRNT_REPS_MAYBE ('0', preczeros);          /* prec */
377 
378   DOPRNT_MEMORY_MAYBE (exponent, explen);      /* exp */
379 
380   if (justify == DOPRNT_JUSTIFY_LEFT)          /* pad for left */
381     DOPRNT_REPS (p->fill, justlen);
382 
383  done:
384   (*__gmp_free_func) (free_ptr, free_size);
385   return retval;
386 
387  error:
388   retval = -1;
389   goto done;
390 }
391