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