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