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