Commit | Line | Data |
---|---|---|
ad0e16d0 | 1 | /* |
5f6de66c KB |
2 | * Copyright (c) 1988 Regents of the University of California. |
3 | * All rights reserved. | |
ad0e16d0 | 4 | * |
5f6de66c KB |
5 | * Redistribution and use in source and binary forms are permitted |
6 | * provided that this notice is preserved and that due credit is given | |
7 | * to the University of California at Berkeley. The name of the University | |
8 | * may not be used to endorse or promote products derived from this | |
9 | * software without specific prior written permission. This software | |
10 | * is provided ``as is'' without express or implied warranty. | |
ad0e16d0 KB |
11 | */ |
12 | ||
5f6de66c | 13 | #if defined(LIBC_SCCS) && !defined(lint) |
2c71023f | 14 | static char sccsid[] = "@(#)vfprintf.c 5.16 (Berkeley) %G%"; |
5f6de66c | 15 | #endif /* LIBC_SCCS and not lint */ |
ad0e16d0 | 16 | |
0d15d742 | 17 | #include <sys/types.h> |
5f6de66c KB |
18 | #include <varargs.h> |
19 | #include <stdio.h> | |
20 | #include <ctype.h> | |
ad0e16d0 | 21 | |
aa876f9d KB |
22 | /* |
23 | * To handle arbitrary floating point precision, the buffer has to hold the | |
24 | * number, a decimal point, and N precision digits. We can't just truncate | |
25 | * at some point is that the lower-level math routines may very well be | |
26 | * repeatedly returning some small fraction. A 128 bit fraction can be | |
27 | * represented in 39 decimal digits. Guess a max of 40 digits of precision, | |
28 | * and add one for the decimal point. | |
29 | */ | |
30 | #define MAXFRAC 39 | |
31 | #define MAXPREC 40 | |
32 | #define MAXDIGIT (MAXFRAC + MAXPREC + 1) | |
6c1ffc8c | 33 | |
f853d467 | 34 | #define PUTC(ch) {++cnt; putc(ch, fp);} |
66d7f790 | 35 | |
410bd0d3 KB |
36 | #define ARG() \ |
37 | _ulong = flags&LONGINT ? va_arg(argp, long) : \ | |
38 | flags&SHORTINT ? va_arg(argp, short) : va_arg(argp, int); | |
39 | ||
40 | /* have to deal with the negative buffer count kludge */ | |
41 | #define NEGATIVE_COUNT_KLUDGE | |
42 | ||
43 | #define LONGINT 0x01 /* long integer */ | |
44 | #define LONGDBL 0x02 /* long double; unimplemented */ | |
45 | #define SHORTINT 0x04 /* short integer */ | |
46 | #define ALT 0x08 /* alternate form */ | |
47 | #define LADJUST 0x10 /* left adjustment */ | |
48 | static int flags; | |
ad0e16d0 | 49 | |
aa876f9d | 50 | static char sign, *buf; |
862bbfbe | 51 | |
66d7f790 | 52 | x_doprnt(fmt, argp, fp) |
5f6de66c KB |
53 | register char *fmt; |
54 | va_list argp; | |
66d7f790 | 55 | register FILE *fp; |
ad0e16d0 | 56 | { |
40233183 KB |
57 | register int cnt, n; |
58 | register char ch, *t; | |
66d7f790 | 59 | double _double; |
40233183 | 60 | u_long _ulong; |
410bd0d3 | 61 | int base, width, prec, size; |
aa876f9d | 62 | char padc, *digs, sbuf[MAXDIGIT]; |
5f6de66c | 63 | |
5592524e | 64 | digs = "0123456789abcdef"; |
aa876f9d | 65 | for (buf = sbuf, cnt = 0;; ++fmt) { |
410bd0d3 KB |
66 | n = fp->_cnt; |
67 | for (t = fp->_ptr; (ch = *fmt) && ch != '%'; ++cnt, ++fmt) | |
68 | if (--n < 0 | |
69 | #ifdef NEGATIVE_COUNT_KLUDGE | |
70 | && (!(fp->_flag & _IOLBF) || -n >= fp->_bufsiz) | |
71 | #endif | |
72 | || ch == '\n' && fp->_flag&_IOLBF) { | |
73 | fp->_cnt = n; | |
74 | fp->_ptr = t; | |
75 | (void)_flsbuf(ch, fp); | |
76 | n = fp->_cnt; | |
77 | t = fp->_ptr; | |
40233183 | 78 | } |
410bd0d3 KB |
79 | else |
80 | *t++ = ch; | |
81 | fp->_cnt = n; | |
82 | fp->_ptr = t; | |
83 | if (!ch) | |
40233183 | 84 | return(cnt); |
ad0e16d0 | 85 | |
410bd0d3 | 86 | flags = width = 0; |
5f6de66c KB |
87 | prec = -1; |
88 | padc = ' '; | |
410bd0d3 | 89 | sign = '\0'; |
5f6de66c | 90 | |
410bd0d3 KB |
91 | rflag: switch (*++fmt) { |
92 | case ' ': | |
93 | sign = ' '; | |
94 | goto rflag; | |
5f6de66c | 95 | case '#': |
410bd0d3 KB |
96 | flags |= ALT; |
97 | goto rflag; | |
5f6de66c | 98 | case '*': |
66d7f790 KB |
99 | /* |
100 | * ``A negative field width argument is taken as a | |
101 | * - flag followed by a positive field width.'' | |
102 | * -- ANSI X3J11 | |
103 | * They don't exclude field widths read from args. | |
104 | */ | |
105 | if ((width = va_arg(argp, int)) >= 0) | |
410bd0d3 | 106 | goto rflag; |
66d7f790 KB |
107 | width = -width; |
108 | /*FALLTHROUGH*/ | |
109 | case '-': | |
410bd0d3 KB |
110 | flags |= LADJUST; |
111 | goto rflag; | |
5f6de66c | 112 | case '+': |
40233183 | 113 | sign = '+'; |
410bd0d3 | 114 | goto rflag; |
5f6de66c | 115 | case '.': |
66d7f790 | 116 | if (*++fmt == '*') |
410bd0d3 KB |
117 | n = va_arg(argp, int); |
118 | else if (isascii(*fmt) && isdigit(*fmt)) { | |
119 | n = 0; | |
5f6de66c | 120 | do { |
410bd0d3 KB |
121 | n = 10 * n + *fmt - '0'; |
122 | } while (isascii(*++fmt) && isdigit(*fmt)); | |
5f6de66c | 123 | --fmt; |
ad0e16d0 | 124 | } |
66d7f790 | 125 | else { |
66d7f790 | 126 | --fmt; |
410bd0d3 KB |
127 | prec = 0; |
128 | goto rflag; | |
66d7f790 | 129 | } |
410bd0d3 KB |
130 | prec = n < 0 ? -1 : n; |
131 | goto rflag; | |
5f6de66c KB |
132 | case '0': |
133 | padc = '0'; | |
66d7f790 | 134 | /*FALLTHROUGH*/ |
5f6de66c KB |
135 | case '1': case '2': case '3': case '4': |
136 | case '5': case '6': case '7': case '8': case '9': | |
410bd0d3 | 137 | n = 0; |
5f6de66c | 138 | do { |
410bd0d3 KB |
139 | n = 10 * n + *fmt - '0'; |
140 | } while (isascii(*++fmt) && isdigit(*fmt)); | |
141 | width = n; | |
5f6de66c | 142 | --fmt; |
aa876f9d | 143 | goto rflag; |
66d7f790 | 144 | case 'L': |
410bd0d3 KB |
145 | /* |
146 | * C doesn't have a long double; use long for now. | |
147 | * flags |= LONGDBL; | |
148 | */ | |
149 | flags |= LONGINT; | |
150 | goto rflag; | |
66d7f790 | 151 | case 'h': |
410bd0d3 KB |
152 | flags |= SHORTINT; |
153 | goto rflag; | |
5f6de66c | 154 | case 'l': |
410bd0d3 KB |
155 | flags |= LONGINT; |
156 | goto rflag; | |
40233183 | 157 | case 'c': |
aa876f9d | 158 | buf[0] = va_arg(argp, int); |
40233183 | 159 | size = 1; |
aa876f9d | 160 | t = buf; |
40233183 | 161 | goto pforw; |
a5676d90 | 162 | case 'd': |
410bd0d3 KB |
163 | case 'i': |
164 | ARG(); | |
165 | if ((long)_ulong < 0) { | |
166 | _ulong = -_ulong; | |
40233183 | 167 | sign = '-'; |
a5676d90 | 168 | } |
40233183 KB |
169 | if (sign) |
170 | PUTC(sign); | |
a5676d90 | 171 | base = 10; |
40233183 | 172 | goto num; |
6c1ffc8c | 173 | case 'e': |
0d15d742 | 174 | case 'E': |
66d7f790 | 175 | case 'f': |
5592524e | 176 | case 'g': |
0d15d742 | 177 | case 'G': |
5592524e | 178 | _double = va_arg(argp, double); |
aa876f9d KB |
179 | size = _cvt(_double, prec, *fmt); |
180 | t = buf; | |
40233183 | 181 | goto pforw; |
66d7f790 | 182 | case 'n': |
410bd0d3 KB |
183 | if (flags&LONGDBL || flags&LONGINT) |
184 | *va_arg(argp, long *) = cnt; | |
185 | else if (flags&SHORTINT) | |
186 | *va_arg(argp, short *) = cnt; | |
187 | else | |
188 | *va_arg(argp, int *) = cnt; | |
66d7f790 | 189 | break; |
ad0e16d0 | 190 | case 'o': |
410bd0d3 | 191 | ARG(); |
ad0e16d0 | 192 | base = 8; |
40233183 | 193 | goto num; |
66d7f790 | 194 | case 'p': |
2c71023f KB |
195 | /* |
196 | * the argument shall be a pointer to void. The value | |
197 | * of the pointer is converted to a sequence of | |
198 | * printable characters, in an implementation-defined | |
199 | * manner. | |
200 | */ | |
201 | _ulong = (u_long)va_arg(argp, void *); | |
202 | base = 16; | |
203 | goto num; | |
ad0e16d0 | 204 | case 's': |
40233183 KB |
205 | if (!(t = va_arg(argp, char *))) |
206 | t = "(null)"; | |
8f77406b | 207 | if ((size = strlen(t)) > prec && prec >= 0) |
40233183 | 208 | size = prec; |
410bd0d3 | 209 | pforw: if (!(flags&LADJUST) && width) |
40233183 KB |
210 | for (n = size; n++ < width;) |
211 | PUTC(padc); | |
212 | if (fp->_cnt - (n = size) >= 0) { | |
213 | cnt += n; | |
214 | fp->_cnt -= n; | |
215 | bcopy(t, fp->_ptr, n); | |
216 | fp->_ptr += n; | |
5f6de66c | 217 | } |
40233183 KB |
218 | else for (; n--; ++t) |
219 | PUTC(*t); | |
410bd0d3 | 220 | if (flags&LADJUST) |
40233183 KB |
221 | while (width-- > size) |
222 | PUTC(padc); | |
ad0e16d0 | 223 | break; |
ad0e16d0 | 224 | case 'u': |
410bd0d3 | 225 | ARG(); |
ad0e16d0 | 226 | base = 10; |
40233183 | 227 | goto num; |
ad0e16d0 KB |
228 | case 'X': |
229 | digs = "0123456789ABCDEF"; | |
5f6de66c | 230 | /*FALLTHROUGH*/ |
ad0e16d0 | 231 | case 'x': |
410bd0d3 | 232 | ARG(); |
40233183 KB |
233 | base = 16; |
234 | /* alternate form for hex; leading 0x/X */ | |
410bd0d3 | 235 | if (flags&ALT && _ulong) { |
f853d467 KB |
236 | PUTC('0'); |
237 | PUTC(*fmt); | |
5f6de66c | 238 | } |
aa876f9d | 239 | num: t = buf + MAXDIGIT - 1; |
5f6de66c | 240 | do { |
40233183 KB |
241 | *t-- = digs[_ulong % base]; |
242 | _ulong /= base; | |
243 | } while(_ulong); | |
244 | digs = "0123456789abcdef"; | |
aa876f9d | 245 | size = buf + MAXDIGIT - 1 - t; |
40233183 KB |
246 | if (size >= prec) { |
247 | /* alternate form for octal; leading 0 */ | |
410bd0d3 | 248 | if (t[1] != '0' && flags&ALT && *fmt == 'o') { |
40233183 KB |
249 | *t-- = '0'; |
250 | ++size; | |
251 | } | |
252 | } | |
253 | else | |
254 | for (; size < prec; ++size) | |
255 | *t-- = '0'; | |
410bd0d3 | 256 | if (!(flags&LADJUST)) |
40233183 | 257 | while (size++ < width) |
f853d467 | 258 | PUTC(padc); |
aa876f9d | 259 | while (++t < buf + MAXDIGIT) |
40233183 | 260 | PUTC(*t); |
66d7f790 | 261 | for (; width > size; --width) |
f853d467 | 262 | PUTC(padc); |
ad0e16d0 | 263 | break; |
5f6de66c | 264 | case '\0': /* "%?" prints ?, unless ? is NULL */ |
40233183 | 265 | return(cnt); |
ad0e16d0 | 266 | default: |
f853d467 | 267 | PUTC(*fmt); |
ad0e16d0 | 268 | } |
ad0e16d0 | 269 | } |
40233183 | 270 | /*NOTREACHED*/ |
ad0e16d0 | 271 | } |
cd0a25f4 | 272 | |
0d15d742 KB |
273 | #define EFORMAT 0x01 |
274 | #define FFORMAT 0x02 | |
275 | #define GFORMAT 0x04 | |
40233183 | 276 | #define DEFPREC 6 |
0d15d742 | 277 | |
aa876f9d KB |
278 | static |
279 | _cvt(number, prec, fmtch) | |
cd0a25f4 | 280 | double number; |
0d15d742 | 281 | register int prec; |
aa876f9d | 282 | char fmtch; |
cd0a25f4 | 283 | { |
862bbfbe | 284 | register char *p; |
0d15d742 | 285 | register int expcnt, format; |
aa876f9d | 286 | static int maxprec = MAXPREC; |
862bbfbe | 287 | double fract, integer, tmp, modf(); |
0d15d742 | 288 | int decpt; |
aa876f9d | 289 | char *endp, *savep, *startp, *malloc(); |
cd0a25f4 | 290 | |
40233183 | 291 | if (prec == -1) |
cd0a25f4 | 292 | prec = DEFPREC; |
cd0a25f4 | 293 | |
aa876f9d KB |
294 | /* allocate space for large precision */ |
295 | if (prec > maxprec) | |
296 | buf = malloc((u_int)((maxprec = prec) + MAXFRAC + 1)); | |
297 | ||
298 | startp = buf; | |
40233183 | 299 | if (number < 0) { |
862bbfbe KB |
300 | *startp++ = '-'; |
301 | number = -number; | |
302 | } | |
40233183 | 303 | else if (sign) |
410bd0d3 | 304 | *startp++ = sign; |
862bbfbe | 305 | |
0d15d742 KB |
306 | switch(fmtch) { |
307 | case 'e': | |
308 | case 'E': | |
309 | format = EFORMAT; | |
310 | break; | |
311 | case 'f': | |
312 | format = FFORMAT; | |
313 | break; | |
314 | case 'g': | |
315 | case 'G': | |
316 | format = GFORMAT; | |
317 | fmtch -= 2; | |
318 | } | |
319 | ||
862bbfbe KB |
320 | /* |
321 | * if the alternate flag is set, or, at least one digit of precision | |
322 | * was requested, add a decimal point, unless it's the g/G format | |
40233183 | 323 | * in which case we require two digits of precision, as it counts |
862bbfbe KB |
324 | * precision differently. |
325 | */ | |
410bd0d3 | 326 | decpt = flags&ALT || prec > (format&GFORMAT ? 1 : 0); |
cd0a25f4 | 327 | |
862bbfbe | 328 | expcnt = 0; |
aa876f9d KB |
329 | p = buf + maxprec + MAXFRAC; |
330 | endp = p + 1; | |
862bbfbe KB |
331 | fract = modf(number, &integer); |
332 | if (integer) { | |
333 | register char *p2; | |
334 | ||
335 | /* get integer part of number; count decimal places */ | |
336 | for (; integer; ++expcnt) { | |
337 | tmp = modf(integer / 10, &integer); | |
338 | *p-- = (int)((tmp + .03) * 10) + '0'; | |
cd0a25f4 | 339 | } |
862bbfbe KB |
340 | |
341 | /* copy, in reverse order, to start of buffer */ | |
342 | p2 = startp; | |
343 | *p2++ = *++p; | |
344 | ||
345 | /* | |
346 | * if the format is g/G, and the resulting exponent will be | |
347 | * greater than the precision, use e/E format. If e/E format, | |
348 | * put in a decimal point as needed, and decrement precision | |
349 | * count for each digit after the decimal point. | |
350 | */ | |
351 | if (format&GFORMAT && expcnt - 1 > prec || format&EFORMAT) { | |
352 | if (format&GFORMAT) { | |
353 | format |= EFORMAT; | |
354 | ||
355 | /* first digit is precision for g/G format */ | |
356 | if (prec) | |
357 | --prec; | |
358 | } | |
359 | if (decpt) | |
360 | *p2++ = '.'; | |
361 | for (; ++p < endp && prec; --prec, *p2++ = *p); | |
362 | ||
410bd0d3 | 363 | /* precision ran out, round */ |
862bbfbe KB |
364 | if (p < endp) { |
365 | if (*p > '4') { | |
366 | for (savep = p2--;; *p2-- = '0') { | |
367 | if (*p2 == '.') | |
368 | --p2; | |
369 | if (++*p2 <= '9') | |
370 | break; | |
371 | } | |
372 | p2 = savep; | |
373 | } | |
374 | fract = 0; | |
375 | } | |
376 | } | |
377 | /* | |
40233183 KB |
378 | * g/G in f format; if out of precision, replace digits with |
379 | * zeroes, note, have to round first. | |
862bbfbe KB |
380 | */ |
381 | else if (format&GFORMAT) { | |
382 | for (; ++p < endp && prec; --prec, *p2++ = *p); | |
383 | /* precision ran out; round and then add zeroes */ | |
384 | if (p < endp) { | |
385 | if (*p > '4') { | |
386 | for (savep = p2--; ++*p2 > '9'; | |
387 | *p2-- = '0'); | |
388 | p2 = savep; | |
389 | } | |
390 | do { | |
391 | *p2++ = '0'; | |
392 | } while (++p < endp); | |
393 | fract = 0; | |
394 | } | |
395 | if (decpt) | |
396 | *p2++ = '.'; | |
cd0a25f4 | 397 | } |
862bbfbe KB |
398 | /* f format */ |
399 | else { | |
400 | for (; ++p < endp; *p2++ = *p); | |
401 | if (decpt) | |
402 | *p2++ = '.'; | |
cd0a25f4 | 403 | } |
862bbfbe KB |
404 | p = p2; |
405 | } | |
406 | /* | |
410bd0d3 KB |
407 | * if no fraction, the number was zero, and if no precision, can't |
408 | * show anything after the decimal point. | |
862bbfbe KB |
409 | */ |
410 | else if (!fract || !prec) { | |
411 | *startp++ = '0'; | |
410bd0d3 | 412 | if (decpt && !(format&GFORMAT)) |
862bbfbe | 413 | *startp++ = '.'; |
410bd0d3 | 414 | *startp = '\0'; |
aa876f9d | 415 | return(startp - buf); |
862bbfbe KB |
416 | } |
417 | /* | |
418 | * if the format is g/G, and the resulting exponent will be less than | |
419 | * -4 use e/E format. If e/E format, compute exponent value. | |
420 | */ | |
421 | else if (format&GFORMAT && fract < .0001 || format&EFORMAT) { | |
422 | format |= EFORMAT; | |
423 | if (fract) | |
424 | for (p = startp; fract;) { | |
425 | fract = modf(fract * 10, &tmp); | |
426 | if (!tmp) { | |
427 | --expcnt; | |
428 | continue; | |
429 | } | |
430 | *p++ = (int)tmp + '0'; | |
431 | break; | |
432 | } | |
cd0a25f4 | 433 | else |
862bbfbe KB |
434 | *p++ = '0'; |
435 | ||
436 | /* g/G format, decrement precision for first digit */ | |
437 | if (format&GFORMAT && prec) | |
438 | --prec; | |
439 | ||
440 | /* add decimal after first non-zero digit */ | |
441 | if (decpt) | |
442 | *p++ = '.'; | |
cd0a25f4 | 443 | } |
862bbfbe KB |
444 | /* |
445 | * f format or g/G printed as f format; don't worry about decimal | |
446 | * point, if g/G format doesn't need it, will get stripped later. | |
447 | */ | |
cd0a25f4 | 448 | else { |
862bbfbe KB |
449 | p = startp; |
450 | *p++ = '0'; | |
451 | *p++ = '.'; | |
452 | } | |
453 | ||
aa876f9d KB |
454 | /* finish out requested precision */ |
455 | while (fract && prec-- > 0) { | |
456 | fract = modf(fract * 10, &tmp); | |
457 | *p++ = (int)tmp + '0'; | |
458 | } | |
459 | while (prec-- > 0) | |
460 | *p++ = '0'; | |
862bbfbe KB |
461 | |
462 | /* | |
463 | * if any fractional value left, "round" it back up to the beginning | |
464 | * of the number, fixing the exponent as necessary, and avoiding the | |
465 | * decimal point. | |
466 | */ | |
467 | if (fract) { | |
468 | (void)modf(fract * 10, &tmp); | |
469 | if (tmp > 4) { | |
470 | for (savep = p--;; *p-- = '0') { | |
471 | if (*p == '.') | |
472 | --p; | |
473 | if (p == startp) { | |
474 | *p = '1'; | |
475 | ++expcnt; | |
476 | break; | |
477 | } | |
478 | if (++*p <= '9') | |
479 | break; | |
480 | } | |
481 | p = savep; | |
cd0a25f4 | 482 | } |
862bbfbe KB |
483 | } |
484 | ||
485 | /* | |
486 | * if a g/G format and not alternate flag, lose trailing zeroes, | |
487 | * if e/E or g/G format, and last char is decimal point, lose it. | |
488 | */ | |
410bd0d3 | 489 | if (!(flags&ALT)) { |
862bbfbe KB |
490 | if (format&GFORMAT) |
491 | for (; p[-1] == '0'; --p); | |
492 | if (format&(GFORMAT|EFORMAT) && p[-1] == '.') | |
493 | --p; | |
494 | } | |
495 | ||
496 | /* if an e/E format, add exponent */ | |
497 | if (format&EFORMAT) { | |
498 | *p++ = fmtch; | |
499 | if (--expcnt < 0) { | |
500 | expcnt = -expcnt; | |
501 | *p++ = '-'; | |
cd0a25f4 | 502 | } |
862bbfbe KB |
503 | else |
504 | *p++ = '+'; | |
505 | *p++ = expcnt / 10 + '0'; | |
506 | *p++ = expcnt % 10 + '0'; | |
cd0a25f4 | 507 | } |
aa876f9d | 508 | return(p - buf); |
cd0a25f4 | 509 | } |