blob: 2a7e0d234fcac5e5480e5e5cd12bdf55200199c4 [file] [log] [blame]
Austin Schuhbb1338c2024-06-15 19:31:16 -07001/* __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
7Copyright 2001, 2002, 2011 Free Software Foundation, Inc.
8
9This file is part of the GNU MP Library.
10
11The GNU MP Library is free software; you can redistribute it and/or modify
12it 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
18or
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
24or both in parallel, as here.
25
26The GNU MP Library is distributed in the hope that it will be useful, but
27WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
28or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29for more details.
30
31You should have received copies of the GNU General Public License and the
32GNU Lesser General Public License along with the GNU MP Library. If not,
33see 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
57int
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}