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) |
f853d467 | 14 | static char sccsid[] = "@(#)vfprintf.c 5.10 (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 | |
0d15d742 KB |
22 | #define MAXBUF 120 |
23 | #define DEFPREC 6 | |
6c1ffc8c | 24 | |
f853d467 | 25 | #define PUTC(ch) {++cnt; putc(ch, fp);} |
66d7f790 KB |
26 | |
27 | #define LONGINT 0x01 | |
28 | #define LONGDBL 0x02 | |
29 | #define SHORTINT 0x04 | |
a5676d90 KB |
30 | #define GETARG(r) \ |
31 | r = argsize&LONGINT ? va_arg(argp, long) : \ | |
32 | argsize&SHORTINT ? va_arg(argp, short) : va_arg(argp, int); | |
ad0e16d0 | 33 | |
862bbfbe KB |
34 | static int alternate; |
35 | static char printsign; | |
36 | ||
66d7f790 | 37 | x_doprnt(fmt, argp, fp) |
5f6de66c KB |
38 | register char *fmt; |
39 | va_list argp; | |
66d7f790 | 40 | register FILE *fp; |
ad0e16d0 | 41 | { |
f853d467 KB |
42 | register int base, cnt; |
43 | register char *bp, *t; | |
5f6de66c KB |
44 | register u_long reg_ulong; |
45 | register long reg_long; | |
66d7f790 | 46 | double _double; |
f853d467 KB |
47 | char argsize, padc, *_cvt(), *digs, buf[MAXBUF]; |
48 | int n, ladjust, width, prec, size; | |
5f6de66c | 49 | |
5592524e | 50 | digs = "0123456789abcdef"; |
5f6de66c KB |
51 | for (cnt = 0; *fmt; ++fmt) { |
52 | if (*fmt != '%') { | |
f853d467 | 53 | PUTC(*fmt); |
5f6de66c | 54 | continue; |
ad0e16d0 KB |
55 | } |
56 | ||
66d7f790 | 57 | alternate = ladjust = width = 0; |
5f6de66c KB |
58 | prec = -1; |
59 | padc = ' '; | |
66d7f790 | 60 | argsize = printsign = '\0'; |
5f6de66c KB |
61 | |
62 | flags: switch (*++fmt) { | |
63 | case '#': | |
64 | alternate = 1; | |
65 | goto flags; | |
5f6de66c | 66 | case '*': |
66d7f790 KB |
67 | /* |
68 | * ``A negative field width argument is taken as a | |
69 | * - flag followed by a positive field width.'' | |
70 | * -- ANSI X3J11 | |
71 | * They don't exclude field widths read from args. | |
72 | */ | |
73 | if ((width = va_arg(argp, int)) >= 0) | |
74 | goto flags; | |
75 | width = -width; | |
76 | /*FALLTHROUGH*/ | |
77 | case '-': | |
78 | ladjust = 1; | |
5f6de66c KB |
79 | goto flags; |
80 | case '+': | |
81 | printsign = '+'; | |
82 | goto flags; | |
5f6de66c | 83 | case '.': |
66d7f790 KB |
84 | if (*++fmt == '*') |
85 | prec = va_arg(argp, int); | |
86 | else if (isdigit(*fmt)) { | |
87 | prec = 0; | |
5f6de66c | 88 | do { |
6c1ffc8c | 89 | prec = 10 * prec + *fmt - '0'; |
5f6de66c KB |
90 | } while isdigit(*++fmt); |
91 | --fmt; | |
ad0e16d0 | 92 | } |
66d7f790 KB |
93 | else { |
94 | prec = 0; | |
95 | --fmt; | |
96 | goto flags; | |
97 | } | |
98 | if (prec < 0) | |
99 | prec = -1; | |
5f6de66c KB |
100 | goto flags; |
101 | case '0': | |
102 | padc = '0'; | |
66d7f790 | 103 | /*FALLTHROUGH*/ |
5f6de66c KB |
104 | case '1': case '2': case '3': case '4': |
105 | case '5': case '6': case '7': case '8': case '9': | |
106 | do { | |
6c1ffc8c | 107 | width = 10 * width + *fmt - '0'; |
5f6de66c KB |
108 | } while isdigit(*++fmt); |
109 | --fmt; | |
66d7f790 KB |
110 | case 'L': |
111 | argsize |= LONGDBL; | |
112 | goto flags; | |
113 | case 'h': | |
114 | argsize |= SHORTINT; | |
115 | goto flags; | |
5f6de66c | 116 | case 'l': |
66d7f790 | 117 | argsize |= LONGINT; |
5f6de66c | 118 | goto flags; |
5592524e KB |
119 | case 'c': { |
120 | char ch; | |
ad0e16d0 | 121 | |
5592524e | 122 | ch = va_arg(argp, int); |
f853d467 | 123 | PUTC(ch); |
66d7f790 | 124 | break; |
5592524e | 125 | } |
a5676d90 KB |
126 | case 'd': |
127 | case 'i': | |
128 | GETARG(reg_long); | |
129 | if (reg_long < 0) { | |
130 | reg_ulong = -reg_long; | |
131 | printsign = '-'; | |
132 | } | |
133 | else { | |
134 | reg_ulong = reg_long; | |
135 | } | |
136 | if (printsign) | |
f853d467 | 137 | PUTC(printsign); |
a5676d90 KB |
138 | base = 10; |
139 | goto num1; | |
6c1ffc8c | 140 | case 'e': |
0d15d742 | 141 | case 'E': |
66d7f790 | 142 | case 'f': |
5592524e | 143 | case 'g': |
0d15d742 | 144 | case 'G': |
5592524e | 145 | _double = va_arg(argp, double); |
0d15d742 | 146 | bp = _cvt(_double, prec, buf, buf + sizeof(buf), *fmt); |
6c1ffc8c | 147 | pbuf: size = bp - buf; |
66d7f790 KB |
148 | if (size < width && !ladjust) |
149 | do { | |
f853d467 | 150 | PUTC(padc); |
66d7f790 KB |
151 | } while (--width > size); |
152 | for (t = buf; t < bp; ++t) | |
f853d467 | 153 | PUTC(*t); |
66d7f790 | 154 | for (; width > size; --width) |
f853d467 | 155 | PUTC(padc); |
ad0e16d0 | 156 | break; |
66d7f790 KB |
157 | case 'n': |
158 | *(va_arg(argp, int *)) = cnt; | |
159 | break; | |
ad0e16d0 | 160 | case 'o': |
66d7f790 | 161 | GETARG(reg_ulong); |
ad0e16d0 | 162 | base = 8; |
66d7f790 KB |
163 | if (!reg_ulong || !alternate) |
164 | goto num1; | |
165 | bp = buf + sizeof(buf) - 1; | |
166 | do { | |
167 | *bp-- = digs[reg_ulong % base]; | |
168 | reg_ulong /= base; | |
169 | } while(reg_ulong); | |
170 | size = &buf[sizeof(buf) - 1] - bp; | |
171 | if (size < --width && !ladjust) | |
172 | do { | |
f853d467 | 173 | PUTC(padc); |
66d7f790 | 174 | } while (--width > size); |
f853d467 | 175 | PUTC('0'); |
6c1ffc8c | 176 | goto num2; |
66d7f790 | 177 | case 'p': |
ad0e16d0 | 178 | case 's': |
66d7f790 KB |
179 | if (!(bp = va_arg(argp, char *))) |
180 | bp = "(null)"; | |
181 | if (width > 0 && !ladjust) { | |
5f6de66c KB |
182 | char *savep; |
183 | ||
66d7f790 KB |
184 | savep = bp; |
185 | for (n = 0; *bp && (prec < 0 || n < prec); | |
186 | n++, bp++); | |
187 | bp = savep; | |
188 | while (n++ < width) | |
f853d467 | 189 | PUTC(' '); |
5f6de66c | 190 | } |
66d7f790 KB |
191 | for (n = 0; *bp; ++bp) { |
192 | if (++n > prec && prec >= 0) | |
ad0e16d0 | 193 | break; |
f853d467 | 194 | PUTC(*bp); |
5f6de66c | 195 | } |
66d7f790 | 196 | if (n < width && ladjust) |
5f6de66c | 197 | do { |
f853d467 | 198 | PUTC(' '); |
66d7f790 | 199 | } while (++n < width); |
ad0e16d0 | 200 | break; |
ad0e16d0 | 201 | case 'u': |
66d7f790 | 202 | GETARG(reg_ulong); |
ad0e16d0 | 203 | base = 10; |
66d7f790 | 204 | goto num1; |
ad0e16d0 KB |
205 | case 'X': |
206 | digs = "0123456789ABCDEF"; | |
5f6de66c | 207 | /*FALLTHROUGH*/ |
ad0e16d0 | 208 | case 'x': |
66d7f790 | 209 | GETARG(reg_ulong); |
5f6de66c | 210 | if (alternate && reg_ulong) { |
f853d467 KB |
211 | PUTC('0'); |
212 | PUTC(*fmt); | |
5f6de66c | 213 | } |
ad0e16d0 | 214 | base = 16; |
66d7f790 | 215 | num1: bp = buf + sizeof(buf) - 1; |
5f6de66c | 216 | do { |
66d7f790 | 217 | *bp-- = digs[reg_ulong % base]; |
5f6de66c KB |
218 | reg_ulong /= base; |
219 | } while(reg_ulong); | |
66d7f790 KB |
220 | size = &buf[sizeof(buf) - 1] - bp; |
221 | for (; size < prec; *bp-- = '0', ++size); | |
222 | if (size < width && !ladjust) | |
223 | do { | |
f853d467 | 224 | PUTC(padc); |
66d7f790 | 225 | } while (--width > size); |
6c1ffc8c | 226 | num2: while (++bp != &buf[MAXBUF]) |
f853d467 | 227 | PUTC(*bp); |
66d7f790 | 228 | for (; width > size; --width) |
f853d467 | 229 | PUTC(padc); |
5592524e | 230 | digs = "0123456789abcdef"; |
ad0e16d0 | 231 | break; |
5f6de66c | 232 | case '\0': /* "%?" prints ?, unless ? is NULL */ |
66d7f790 | 233 | return(ferror(fp) ? -1 : cnt); |
ad0e16d0 | 234 | default: |
f853d467 | 235 | PUTC(*fmt); |
ad0e16d0 | 236 | } |
ad0e16d0 | 237 | } |
66d7f790 | 238 | return(ferror(fp) ? -1 : cnt); |
ad0e16d0 | 239 | } |
cd0a25f4 | 240 | |
0d15d742 KB |
241 | #define EFORMAT 0x01 |
242 | #define FFORMAT 0x02 | |
243 | #define GFORMAT 0x04 | |
244 | ||
245 | static char * | |
246 | _cvt(number, prec, startp, endp, fmtch) | |
cd0a25f4 | 247 | double number; |
0d15d742 | 248 | register int prec; |
862bbfbe | 249 | char *startp, *endp, fmtch; |
cd0a25f4 | 250 | { |
862bbfbe | 251 | register char *p; |
0d15d742 | 252 | register int expcnt, format; |
862bbfbe | 253 | double fract, integer, tmp, modf(); |
0d15d742 | 254 | int decpt; |
862bbfbe | 255 | char *savep; |
cd0a25f4 | 256 | |
862bbfbe | 257 | if (prec == -1) /* set default precision */ |
cd0a25f4 | 258 | prec = DEFPREC; |
cd0a25f4 | 259 | |
862bbfbe KB |
260 | p = endp - 1; |
261 | if (number < 0) { /* set sign */ | |
262 | *startp++ = '-'; | |
263 | number = -number; | |
264 | } | |
cd0a25f4 | 265 | else if (printsign) |
862bbfbe KB |
266 | *startp++ = '+'; |
267 | ||
0d15d742 KB |
268 | switch(fmtch) { |
269 | case 'e': | |
270 | case 'E': | |
271 | format = EFORMAT; | |
272 | break; | |
273 | case 'f': | |
274 | format = FFORMAT; | |
275 | break; | |
276 | case 'g': | |
277 | case 'G': | |
278 | format = GFORMAT; | |
279 | fmtch -= 2; | |
280 | } | |
281 | ||
862bbfbe KB |
282 | /* |
283 | * if the alternate flag is set, or, at least one digit of precision | |
284 | * was requested, add a decimal point, unless it's the g/G format | |
285 | * in which case we require two digits of precision, since it counts | |
286 | * precision differently. | |
287 | */ | |
288 | decpt = alternate || prec > 1 || !(format&GFORMAT) && prec; | |
cd0a25f4 | 289 | |
862bbfbe KB |
290 | expcnt = 0; |
291 | fract = modf(number, &integer); | |
292 | if (integer) { | |
293 | register char *p2; | |
294 | ||
295 | /* get integer part of number; count decimal places */ | |
296 | for (; integer; ++expcnt) { | |
297 | tmp = modf(integer / 10, &integer); | |
298 | *p-- = (int)((tmp + .03) * 10) + '0'; | |
cd0a25f4 | 299 | } |
862bbfbe KB |
300 | |
301 | /* copy, in reverse order, to start of buffer */ | |
302 | p2 = startp; | |
303 | *p2++ = *++p; | |
304 | ||
305 | /* | |
306 | * if the format is g/G, and the resulting exponent will be | |
307 | * greater than the precision, use e/E format. If e/E format, | |
308 | * put in a decimal point as needed, and decrement precision | |
309 | * count for each digit after the decimal point. | |
310 | */ | |
311 | if (format&GFORMAT && expcnt - 1 > prec || format&EFORMAT) { | |
312 | if (format&GFORMAT) { | |
313 | format |= EFORMAT; | |
314 | ||
315 | /* first digit is precision for g/G format */ | |
316 | if (prec) | |
317 | --prec; | |
318 | } | |
319 | if (decpt) | |
320 | *p2++ = '.'; | |
321 | for (; ++p < endp && prec; --prec, *p2++ = *p); | |
322 | ||
323 | /* precision ran out; round number */ | |
324 | if (p < endp) { | |
325 | if (*p > '4') { | |
326 | for (savep = p2--;; *p2-- = '0') { | |
327 | if (*p2 == '.') | |
328 | --p2; | |
329 | if (++*p2 <= '9') | |
330 | break; | |
331 | } | |
332 | p2 = savep; | |
333 | } | |
334 | fract = 0; | |
335 | } | |
336 | } | |
337 | /* | |
338 | * g/G in f format; if run out of precision, replace digits | |
339 | * with zeroes, note, have to round first, otherwise lose | |
340 | * rounding point. | |
341 | */ | |
342 | else if (format&GFORMAT) { | |
343 | for (; ++p < endp && prec; --prec, *p2++ = *p); | |
344 | /* precision ran out; round and then add zeroes */ | |
345 | if (p < endp) { | |
346 | if (*p > '4') { | |
347 | for (savep = p2--; ++*p2 > '9'; | |
348 | *p2-- = '0'); | |
349 | p2 = savep; | |
350 | } | |
351 | do { | |
352 | *p2++ = '0'; | |
353 | } while (++p < endp); | |
354 | fract = 0; | |
355 | } | |
356 | if (decpt) | |
357 | *p2++ = '.'; | |
cd0a25f4 | 358 | } |
862bbfbe KB |
359 | /* f format */ |
360 | else { | |
361 | for (; ++p < endp; *p2++ = *p); | |
362 | if (decpt) | |
363 | *p2++ = '.'; | |
cd0a25f4 | 364 | } |
862bbfbe KB |
365 | p = p2; |
366 | } | |
367 | /* | |
368 | * it's unclear from the ANSI X3J11 spec if the g/G format should | |
369 | * just result in an empty string, because it's supposed to remove | |
370 | * trailing zeroes. That seems counter-intuitive, so here it does | |
371 | * what f and e/E do; if no fraction, the number was zero, and if | |
372 | * no precision can't show anything after the decimal point. | |
373 | */ | |
374 | else if (!fract || !prec) { | |
375 | *startp++ = '0'; | |
376 | if (decpt) | |
377 | *startp++ = '.'; | |
378 | *startp++ = '\0'; | |
379 | return(startp); | |
380 | } | |
381 | /* | |
382 | * if the format is g/G, and the resulting exponent will be less than | |
383 | * -4 use e/E format. If e/E format, compute exponent value. | |
384 | */ | |
385 | else if (format&GFORMAT && fract < .0001 || format&EFORMAT) { | |
386 | format |= EFORMAT; | |
387 | if (fract) | |
388 | for (p = startp; fract;) { | |
389 | fract = modf(fract * 10, &tmp); | |
390 | if (!tmp) { | |
391 | --expcnt; | |
392 | continue; | |
393 | } | |
394 | *p++ = (int)tmp + '0'; | |
395 | break; | |
396 | } | |
cd0a25f4 | 397 | else |
862bbfbe KB |
398 | *p++ = '0'; |
399 | ||
400 | /* g/G format, decrement precision for first digit */ | |
401 | if (format&GFORMAT && prec) | |
402 | --prec; | |
403 | ||
404 | /* add decimal after first non-zero digit */ | |
405 | if (decpt) | |
406 | *p++ = '.'; | |
cd0a25f4 | 407 | } |
862bbfbe KB |
408 | /* |
409 | * f format or g/G printed as f format; don't worry about decimal | |
410 | * point, if g/G format doesn't need it, will get stripped later. | |
411 | */ | |
cd0a25f4 | 412 | else { |
862bbfbe KB |
413 | p = startp; |
414 | *p++ = '0'; | |
415 | *p++ = '.'; | |
416 | } | |
417 | ||
418 | /* finish out requested precision from fractional value */ | |
419 | while (prec--) | |
420 | if (fract) { | |
421 | fract = modf(fract * 10, &tmp); | |
422 | *p++ = (int)tmp + '0'; | |
cd0a25f4 | 423 | } |
862bbfbe KB |
424 | else |
425 | *p++ = '0'; | |
426 | ||
427 | /* | |
428 | * if any fractional value left, "round" it back up to the beginning | |
429 | * of the number, fixing the exponent as necessary, and avoiding the | |
430 | * decimal point. | |
431 | */ | |
432 | if (fract) { | |
433 | (void)modf(fract * 10, &tmp); | |
434 | if (tmp > 4) { | |
435 | for (savep = p--;; *p-- = '0') { | |
436 | if (*p == '.') | |
437 | --p; | |
438 | if (p == startp) { | |
439 | *p = '1'; | |
440 | ++expcnt; | |
441 | break; | |
442 | } | |
443 | if (++*p <= '9') | |
444 | break; | |
445 | } | |
446 | p = savep; | |
cd0a25f4 | 447 | } |
862bbfbe KB |
448 | } |
449 | ||
450 | /* | |
451 | * if a g/G format and not alternate flag, lose trailing zeroes, | |
452 | * if e/E or g/G format, and last char is decimal point, lose it. | |
453 | */ | |
454 | if (!alternate) { | |
455 | if (format&GFORMAT) | |
456 | for (; p[-1] == '0'; --p); | |
457 | if (format&(GFORMAT|EFORMAT) && p[-1] == '.') | |
458 | --p; | |
459 | } | |
460 | ||
461 | /* if an e/E format, add exponent */ | |
462 | if (format&EFORMAT) { | |
463 | *p++ = fmtch; | |
464 | if (--expcnt < 0) { | |
465 | expcnt = -expcnt; | |
466 | *p++ = '-'; | |
cd0a25f4 | 467 | } |
862bbfbe KB |
468 | else |
469 | *p++ = '+'; | |
470 | *p++ = expcnt / 10 + '0'; | |
471 | *p++ = expcnt % 10 + '0'; | |
cd0a25f4 | 472 | } |
862bbfbe | 473 | return(p); |
cd0a25f4 | 474 | } |