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 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. */ 93 ndigits = prec + 2 94 + EXP(f) * (mp_bases[ABS(p->base)].chars_per_limb + (EXP(f)>=0)); 95 ndigits = MAX (ndigits, 1); 96 break; 97 98 case DOPRNT_CONV_SCIENTIFIC: 99 /* precision is digits after the radix point, and there's one digit 100 before */ 101 ndigits = prec + 1; 102 break; 103 104 default: 105 ASSERT (0); 106 /*FALLTHRU*/ 107 108 case DOPRNT_CONV_GENERAL: 109 /* precision is total digits, but be sure to ask mpf_get_str for at 110 least 1, not 0 */ 111 ndigits = MAX (prec, 1); 112 break; 113 } 114 } 115 TRACE (printf (" ndigits %d\n", ndigits)); 116 117 s = mpf_get_str (NULL, &exp, p->base, ndigits, f); 118 len = strlen (s); 119 free_ptr = s; 120 free_size = len + 1; 121 TRACE (printf (" s %s\n", s); 122 printf (" exp %ld\n", exp); 123 printf (" len %d\n", len)); 124 125 /* For fixed mode check the ndigits formed above was in fact enough for 126 the integer part plus p->prec after the radix point. */ 127 ASSERT ((p->conv == DOPRNT_CONV_FIXED && p->prec > -1) 128 ? ndigits >= MAX (1, exp + p->prec + 2) : 1); 129 130 sign = p->sign; 131 if (s[0] == '-') 132 { 133 sign = s[0]; 134 s++, len--; 135 } 136 signlen = (sign != '\0'); 137 TRACE (printf (" sign %c signlen %d\n", sign, signlen)); 138 139 switch (p->conv) { 140 case DOPRNT_CONV_FIXED: 141 if (prec <= -1) 142 prec = MAX (0, len-exp); /* retain all digits */ 143 144 /* Truncate if necessary so fraction will be at most prec digits. */ 145 ASSERT (prec >= 0); 146 newlen = exp + prec; 147 if (newlen < 0) 148 { 149 /* first non-zero digit is below target prec, and at least one zero 150 digit in between, so print zero */ 151 len = 0; 152 exp = 0; 153 } 154 else if (len <= newlen) 155 { 156 /* already got few enough digits */ 157 } 158 else 159 { 160 /* discard excess digits and round to nearest */ 161 162 const char *num_to_text = (p->base >= 0 163 ? "0123456789abcdefghijklmnopqrstuvwxyz" 164 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 165 int base = ABS(p->base); 166 int n; 167 168 ASSERT (base <= 36); 169 170 len = newlen; 171 n = DIGIT_VALUE (s[len]); 172 TRACE (printf (" rounding with %d\n", n)); 173 if (n >= (base + 1) / 2) 174 { 175 /* propagate a carry */ 176 for (;;) 177 { 178 if (len == 0) 179 { 180 s[0] = '1'; 181 len = 1; 182 exp++; 183 break; 184 } 185 n = DIGIT_VALUE (s[len-1]); 186 ASSERT (n >= 0 && n < base); 187 n++; 188 if (n != base) 189 { 190 TRACE (printf (" storing now %d\n", n)); 191 s[len-1] = num_to_text[n]; 192 break; 193 } 194 len--; 195 } 196 } 197 else 198 { 199 /* truncate only, strip any trailing zeros now exposed */ 200 while (len > 0 && s[len-1] == '0') 201 len--; 202 } 203 204 /* Can have newlen==0, in which case the truncate was just to check 205 for a carry turning it into "1". If we're left with len==0 then 206 adjust exp to match. */ 207 if (len == 0) 208 exp = 0; 209 } 210 211 fixed: 212 ASSERT (len == 0 ? exp == 0 : 1); 213 if (exp <= 0) 214 { 215 TRACE (printf (" fixed 0.000sss\n")); 216 intlen = 0; 217 intzeros = 1; 218 fraczeros = -exp; 219 fraclen = len; 220 } 221 else 222 { 223 TRACE (printf (" fixed sss.sss or sss000\n")); 224 intlen = MIN (len, exp); 225 intzeros = exp - intlen; 226 fraczeros = 0; 227 fraclen = len - intlen; 228 } 229 explen = 0; 230 break; 231 232 case DOPRNT_CONV_SCIENTIFIC: 233 { 234 long int expval; 235 char expsign; 236 237 if (prec <= -1) 238 prec = MAX (0, len-1); /* retain all digits */ 239 240 scientific: 241 TRACE (printf (" scientific s.sss\n")); 242 243 intlen = MIN (1, len); 244 intzeros = (intlen == 0 ? 1 : 0); 245 fraczeros = 0; 246 fraclen = len - intlen; 247 248 expval = (exp-intlen); 249 if (p->exptimes4) 250 expval <<= 2; 251 252 /* Split out the sign since %o or %x in expfmt give negatives as twos 253 complement, not with a sign. */ 254 expsign = (expval >= 0 ? '+' : '-'); 255 expval = ABS (expval); 256 257 #if HAVE_VSNPRINTF 258 explen = snprintf (exponent, sizeof(exponent), 259 p->expfmt, expsign, expval); 260 /* test for < sizeof-1 since a glibc 2.0.x return of sizeof-1 might 261 mean truncation */ 262 ASSERT (explen >= 0 && explen < sizeof(exponent)-1); 263 #else 264 sprintf (exponent, p->expfmt, expsign, expval); 265 explen = strlen (exponent); 266 ASSERT (explen < sizeof(exponent)); 267 #endif 268 TRACE (printf (" expfmt %s gives %s\n", p->expfmt, exponent)); 269 } 270 break; 271 272 default: 273 ASSERT (0); 274 /*FALLTHRU*/ /* to stop variables looking uninitialized */ 275 276 case DOPRNT_CONV_GENERAL: 277 /* The exponent for "scientific" will be exp-1, choose scientific if 278 this is < -4 or >= prec (and minimum 1 for prec). For f==0 will have 279 exp==0 and get the desired "fixed". This rule follows glibc. For 280 fixed there's no need to truncate, the desired ndigits will already 281 be as required. */ 282 if (exp-1 < -4 || exp-1 >= MAX (1, prec)) 283 goto scientific; 284 else 285 goto fixed; 286 } 287 288 TRACE (printf (" intlen %d intzeros %d fraczeros %d fraclen %d\n", 289 intlen, intzeros, fraczeros, fraclen)); 290 ASSERT (p->prec <= -1 291 ? intlen + fraclen == strlen (s) 292 : intlen + fraclen <= strlen (s)); 293 294 if (p->showtrailing) 295 { 296 /* Pad to requested precision with trailing zeros, for general this is 297 all digits, for fixed and scientific just the fraction. */ 298 preczeros = prec - (fraczeros + fraclen 299 + (p->conv == DOPRNT_CONV_GENERAL 300 ? intlen + intzeros : 0)); 301 preczeros = MAX (0, preczeros); 302 } 303 else 304 preczeros = 0; 305 TRACE (printf (" prec=%d showtrailing=%d, pad with preczeros %d\n", 306 prec, p->showtrailing, preczeros)); 307 308 /* radix point if needed, or if forced */ 309 pointlen = ((fraczeros + fraclen + preczeros) != 0 || p->showpoint != 0) 310 ? strlen (point) : 0; 311 TRACE (printf (" point |%s| pointlen %d\n", point, pointlen)); 312 313 /* Notice the test for a non-zero value is done after any truncation for 314 DOPRNT_CONV_FIXED. */ 315 showbase = NULL; 316 showbaselen = 0; 317 switch (p->showbase) { 318 default: 319 ASSERT (0); 320 /*FALLTHRU*/ 321 case DOPRNT_SHOWBASE_NO: 322 break; 323 case DOPRNT_SHOWBASE_NONZERO: 324 if (intlen == 0 && fraclen == 0) 325 break; 326 /*FALLTHRU*/ 327 case DOPRNT_SHOWBASE_YES: 328 switch (p->base) { 329 case 16: showbase = "0x"; showbaselen = 2; break; 330 case -16: showbase = "0X"; showbaselen = 2; break; 331 case 8: showbase = "0"; showbaselen = 1; break; 332 } 333 break; 334 } 335 TRACE (printf (" showbase %s showbaselen %d\n", 336 showbase == NULL ? "" : showbase, showbaselen)); 337 338 /* left over field width */ 339 justlen = p->width - (signlen + showbaselen + intlen + intzeros + pointlen 340 + fraczeros + fraclen + preczeros + explen); 341 TRACE (printf (" justlen %d fill 0x%X\n", justlen, p->fill)); 342 343 justify = p->justify; 344 if (justlen <= 0) /* no justifying if exceed width */ 345 justify = DOPRNT_JUSTIFY_NONE; 346 347 TRACE (printf (" justify type %d intlen %d pointlen %d fraclen %d\n", 348 justify, intlen, pointlen, fraclen)); 349 350 if (justify == DOPRNT_JUSTIFY_RIGHT) /* pad for right */ 351 DOPRNT_REPS (p->fill, justlen); 352 353 if (signlen) /* sign */ 354 DOPRNT_REPS (sign, 1); 355 356 DOPRNT_MEMORY_MAYBE (showbase, showbaselen); /* base */ 357 358 if (justify == DOPRNT_JUSTIFY_INTERNAL) /* pad for internal */ 359 DOPRNT_REPS (p->fill, justlen); 360 361 DOPRNT_MEMORY (s, intlen); /* integer */ 362 DOPRNT_REPS_MAYBE ('0', intzeros); 363 364 DOPRNT_MEMORY_MAYBE (point, pointlen); /* point */ 365 366 DOPRNT_REPS_MAYBE ('0', fraczeros); /* frac */ 367 DOPRNT_MEMORY_MAYBE (s+intlen, fraclen); 368 369 DOPRNT_REPS_MAYBE ('0', preczeros); /* prec */ 370 371 DOPRNT_MEMORY_MAYBE (exponent, explen); /* exp */ 372 373 if (justify == DOPRNT_JUSTIFY_LEFT) /* pad for left */ 374 DOPRNT_REPS (p->fill, justlen); 375 376 done: 377 (*__gmp_free_func) (free_ptr, free_size); 378 return retval; 379 380 error: 381 retval = -1; 382 goto done; 383 } 384