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