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