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
__gmp_doprnt_mpf(const struct doprnt_funs_t * funs,void * data,const struct doprnt_params_t * p,const char * point,mpf_srcptr f)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