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