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 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