home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1988 Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley. The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- */
-
- #if defined(LIBC_SCCS) && !defined(lint)
- static char sccsid[] = "@(#)doprnt.c 5.35 (Berkeley) 6/27/88";
- #endif /* LIBC_SCCS and not lint */
-
- #include <sys/types.h>
- #include <varargs.h>
- #include <stdio.h>
- #include <ctype.h>
-
- #ifdef SYSV
- typedef unsigned char u_char;
- typedef unsigned long u_long;
- #endif
-
- /* 11-bit exponent (VAX G floating point) is 308 decimal digits */
- #define MAXEXP 308
- /* 128 bit fraction takes up 39 decimal digits; max reasonable precision */
- #define MAXFRACT 39
-
- #define DEFPREC 6
-
- #define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
-
- #define PUTC(ch) (void) putc(ch, fp)
-
- #define ARG() \
- _ulong = flags&LONGINT ? va_arg(argp, long) : \
- flags&SHORTINT ? va_arg(argp, short) : va_arg(argp, int);
-
- #define todigit(c) ((c) - '0')
- #define tochar(n) ((n) + '0')
-
- /* have to deal with the negative buffer count kludge */
- #define NEGATIVE_COUNT_KLUDGE
-
- #define LONGINT 0x01 /* long integer */
- #define LONGDBL 0x02 /* long double; unimplemented */
- #define SHORTINT 0x04 /* short integer */
- #define ALT 0x08 /* alternate form */
- #define LADJUST 0x10 /* left adjustment */
- #define ZEROPAD 0x20 /* zero (as opposed to blank) pad */
- #define HEXPREFIX 0x40 /* add 0x or 0X prefix */
-
- extern double __plinf, __neinf, __nan;
- /* double __plinf = 1e500; */
- char *__inf_str = "#INF";
-
- /* double __neinf = -1e500; */
- char *__ninf_str = "#NINF";
-
- /* __asm(".globl ___nan");
- __asm("___nan: .double 0rnan"); */
- /* double __nan = __asm("0rnan"); */
- char *__nan_str = "#NAN";
-
- static int cvt();
- static char *round();
- static char *exponent();
-
- extern double modf();
- extern int strlen();
- extern void bcopy();
-
- int
- _doprnt(fmt0, argp, fp)
- u_char *fmt0;
- va_list argp;
- register FILE *fp;
- {
- register u_char *fmt; /* format string */
- register int ch; /* character from fmt */
- register int cnt; /* return value accumulator */
- register int n; /* random handy integer */
- register char *t; /* buffer pointer */
- double _double; /* double precision arguments %[eEfgG] */
- u_long _ulong; /* integer arguments %[diouxX] */
- int base; /* base for [diouxX] conversion */
- int dprec; /* decimal precision in [diouxX] */
- int fieldsz; /* field size expanded by sign, etc */
- int flags; /* flags as above */
- int fpprec; /* `extra' floating precision in [eEfgG] */
- int prec; /* precision from format (%.3d), or -1 */
- int realsz; /* field size expanded by decimal precision */
- int size; /* size of converted field or string */
- int width; /* width from format (%8d), or 0 */
- char sign; /* sign prefix (' ', '+', '-', or \0) */
- char softsign; /* temporary negative sign for floats */
- char *digs; /* digits for [diouxX] conversion */
- char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */
-
- if (fp->_flag & _IORW) {
- fp->_flag |= _IOWRT;
- fp->_flag &= ~(_IOEOF|_IOREAD);
- }
- if ((fp->_flag & _IOWRT) == 0)
- return (EOF);
-
- fmt = fmt0;
- digs = "0123456789abcdef";
- for (cnt = 0;; ++fmt) {
- n = fp->_cnt;
- for (t = (char *)fp->_ptr; (ch = *fmt) && ch != '%';
- ++cnt, ++fmt)
- if (--n < 0
- #ifdef NEGATIVE_COUNT_KLUDGE
- && (!(fp->_flag & _IOLBF) || -n >= fp->_bufsiz)
- #endif
- || ch == '\n' && fp->_flag & _IOLBF) {
- fp->_cnt = n;
- fp->_ptr = t;
- (void) _flsbuf((u_char)ch, fp);
- n = fp->_cnt;
- t = (char *)fp->_ptr;
- } else
- *t++ = ch;
- fp->_cnt = n;
- fp->_ptr = t;
- if (!ch)
- return (cnt);
-
- flags = 0; dprec = 0; fpprec = 0; width = 0;
- prec = -1;
- sign = '\0';
-
- rflag: switch (*++fmt) {
- case ' ':
- /*
- * ``If the space and + flags both appear, the space
- * flag will be ignored.''
- * -- ANSI X3J11
- */
- if (!sign)
- sign = ' ';
- goto rflag;
- case '#':
- flags |= ALT;
- goto rflag;
- case '*':
- /*
- * ``A negative field width argument is taken as a
- * - flag followed by a positive field width.''
- * -- ANSI X3J11
- * They don't exclude field widths read from args.
- */
- if ((width = va_arg(argp, int)) >= 0)
- goto rflag;
- width = -width;
- /* FALLTHROUGH */
- case '-':
- flags |= LADJUST;
- goto rflag;
- case '+':
- sign = '+';
- goto rflag;
- case '.':
- if (*++fmt == '*')
- n = va_arg(argp, int);
- else {
- n = 0;
- while (isascii(*fmt) && isdigit(*fmt))
- n = 10 * n + todigit(*fmt++);
- --fmt;
- }
- prec = n < 0 ? -1 : n;
- goto rflag;
- case '0':
- /*
- * ``Note that 0 is taken as a flag, not as the
- * beginning of a field width.''
- * -- ANSI X3J11
- */
- flags |= ZEROPAD;
- goto rflag;
- case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- n = 0;
- do {
- n = 10 * n + todigit(*fmt);
- } while (isascii(*++fmt) && isdigit(*fmt));
- width = n;
- --fmt;
- goto rflag;
- case 'L':
- flags |= LONGDBL;
- goto rflag;
- case 'h':
- flags |= SHORTINT;
- goto rflag;
- case 'l':
- flags |= LONGINT;
- goto rflag;
- case 'c':
- *(t = buf) = va_arg(argp, int);
- size = 1;
- sign = '\0';
- goto pforw;
- case 'D':
- flags |= LONGINT;
- /*FALLTHROUGH*/
- case 'd':
- case 'i':
- ARG();
- if ((long)_ulong < 0) {
- _ulong = -_ulong;
- sign = '-';
- }
- base = 10;
- goto number;
- case 'e':
- case 'E':
- case 'f':
- case 'g':
- case 'G':
- _double = va_arg(argp, double);
- /*
- * don't do unrealistic precision; just pad it with
- * zeroes later, so buffer size stays rational.
- */
- if (prec > MAXFRACT) {
- if (*fmt != 'g' && *fmt != 'G' || (flags&ALT))
- fpprec = prec - MAXFRACT;
- prec = MAXFRACT;
- }
- else if (prec == -1)
- prec = DEFPREC;
- /*
- * softsign avoids negative 0 if _double is < 0 and
- * no significant digits will be shown
- */
- if (_double < 0) {
- softsign = '-';
- _double = -_double;
- }
- else
- softsign = 0;
- /*
- * cvt may have to round up past the "start" of the
- * buffer, i.e. ``intf("%.2f", (double)9.999);'';
- * if the first char isn't NULL, it did.
- */
- *buf = NULL;
- size = cvt(_double, prec, flags, &softsign, *fmt, buf,
- buf + sizeof(buf));
- if (softsign)
- sign = '-';
- t = *buf ? buf : buf + 1;
- goto pforw;
- case 'n':
- if (flags & LONGINT)
- *va_arg(argp, long *) = cnt;
- else if (flags & SHORTINT)
- *va_arg(argp, short *) = cnt;
- else
- *va_arg(argp, int *) = cnt;
- break;
- case 'O':
- flags |= LONGINT;
- /*FALLTHROUGH*/
- case 'o':
- ARG();
- base = 8;
- goto nosign;
- case 'p':
- /*
- * ``The argument shall be a pointer to void. The
- * value of the pointer is converted to a sequence
- * of printable characters, in an implementation-
- * defined manner.''
- * -- ANSI X3J11
- */
- /* NOSTRICT */
- _ulong = (u_long)va_arg(argp, void *);
- base = 16;
- goto nosign;
- case 's':
- if (!(t = va_arg(argp, char *)))
- t = "(null)";
- if (prec >= 0) {
- /*
- * can't use strlen; can only look for the
- * NUL in the first `prec' characters, and
- * strlen() will go further.
- */
- char *p, *memchr();
-
- if (p = memchr(t, 0, prec)) {
- size = p - t;
- if (size > prec)
- size = prec;
- } else
- size = prec;
- } else
- size = strlen(t);
- sign = '\0';
- goto pforw;
- case 'U':
- flags |= LONGINT;
- /*FALLTHROUGH*/
- case 'u':
- ARG();
- base = 10;
- goto nosign;
- case 'X':
- digs = "0123456789ABCDEF";
- /* FALLTHROUGH */
- case 'x':
- ARG();
- base = 16;
- /* leading 0x/X only if non-zero */
- if (flags & ALT && _ulong != 0)
- flags |= HEXPREFIX;
-
- /* unsigned conversions */
- nosign: sign = '\0';
- /*
- * ``... diouXx conversions ... if a precision is
- * specified, the 0 flag will be ignored.''
- * -- ANSI X3J11
- */
- number: if ((dprec = prec) >= 0)
- flags &= ~ZEROPAD;
-
- /*
- * ``The result of converting a zero value with an
- * explicit precision of zero is no characters.''
- * -- ANSI X3J11
- */
- t = buf + BUF;
- if (_ulong != 0 || prec != 0) {
- do {
- *--t = digs[_ulong % base];
- _ulong /= base;
- } while (_ulong);
- digs = "0123456789abcdef";
- if (flags & ALT && base == 8 && *t != '0')
- *--t = '0'; /* octal leading 0 */
- }
- size = buf + BUF - t;
-
- pforw:
- /*
- * All reasonable formats wind up here. At this point,
- * `t' points to a string which (if not flags&LADJUST)
- * should be padded out to `width' places. If
- * flags&ZEROPAD, it should first be prefixed by any
- * sign or other prefix; otherwise, it should be blank
- * padded before the prefix is emitted. After any
- * left-hand padding and prefixing, emit zeroes
- * required by a decimal [diouxX] precision, then print
- * the string proper, then emit zeroes required by any
- * leftover floating precision; finally, if LADJUST,
- * pad with blanks.
- */
-
- /*
- * compute actual size, so we know how much to pad
- * fieldsz excludes decimal prec; realsz includes it
- */
- fieldsz = size + fpprec;
- if (sign)
- fieldsz++;
- if (flags & HEXPREFIX)
- fieldsz += 2;
- realsz = dprec > fieldsz ? dprec : fieldsz;
-
- /* right-adjusting blank padding */
- if ((flags & (LADJUST|ZEROPAD)) == 0 && width)
- for (n = realsz; n < width; n++)
- PUTC(' ');
- /* prefix */
- if (sign)
- PUTC(sign);
- if (flags & HEXPREFIX) {
- PUTC('0');
- PUTC((char)*fmt);
- }
- /* right-adjusting zero padding */
- if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
- for (n = realsz; n < width; n++)
- PUTC('0');
- /* leading zeroes from decimal precision */
- for (n = fieldsz; n < dprec; n++)
- PUTC('0');
-
- /* the string or number proper */
- if (fp->_cnt - (n = size) >= 0 &&
- (fp->_flag & _IOLBF) == 0) {
- fp->_cnt -= n;
- bcopy(t, (char *)fp->_ptr, n);
- fp->_ptr += n;
- } else
- while (--n >= 0)
- PUTC(*t++);
- /* trailing f.p. zeroes */
- while (--fpprec >= 0)
- PUTC('0');
- /* left-adjusting padding (always blank) */
- if (flags & LADJUST)
- for (n = realsz; n < width; n++)
- PUTC(' ');
- /* finally, adjust cnt */
- cnt += width > realsz ? width : realsz;
- break;
- case '\0': /* "%?" prints ?, unless ? is NULL */
- return (cnt);
- default:
- PUTC((char)*fmt);
- cnt++;
- }
- }
- /* NOTREACHED */
- }
-
- static int
- cvt(number, prec, flags, signp, fmtch, startp, endp)
- double number;
- register int prec;
- int flags;
- u_char fmtch;
- char *signp, *startp, *endp;
- {
- register char *p, *t;
- register double fract;
- int dotrim, expcnt, gformat;
- double integer, tmp, modf();
-
- /* JF for numbers */
- if(number!=number) {
- t= ++startp;
- p=__nan_str;
- while(*p)
- *t++= *p++;
- return t-startp;
- }
- if(number==__plinf) {
- t= ++startp;
- p= (*signp) ? __ninf_str : __inf_str;
- *signp=0;
- while(*p)
- *t++= *p++;
- return t-startp;
- }
- /* if(number==__neinf) {
- t= ++startp;
- p=__ninf_str;
- while(*p)
- *t++= *p++;
- return t-startp;
- } */
- dotrim = expcnt = gformat = 0;
- fract = modf(number, &integer);
-
- /* get an extra slot for rounding. */
- t = ++startp;
-
- /*
- * get integer portion of number; put into the end of the buffer; the
- * .01 is added for modf(356.0 / 10, &integer) returning .59999999...
- */
- for (p = endp - 1; integer; ++expcnt) {
- tmp = modf(integer / 10, &integer);
- *p-- = tochar((int)((tmp + .01) * 10));
- }
- switch(fmtch) {
- case 'f':
- /* reverse integer into beginning of buffer */
- if (expcnt)
- for (; ++p < endp; *t++ = *p);
- else
- *t++ = '0';
- /*
- * if precision required or alternate flag set, add in a
- * decimal point.
- */
- if (prec || flags&ALT)
- *t++ = '.';
- /* if requires more precision and some fraction left */
- if (fract) {
- if (prec)
- do {
- fract = modf(fract * 10, &tmp);
- *t++ = tochar((int)tmp);
- } while (--prec && fract);
- if (fract)
- startp = round(fract, (int *)NULL, startp,
- t - 1, (char)0, signp);
- }
- for (; prec--; *t++ = '0');
- break;
- case 'e':
- case 'E':
- eformat: if (expcnt) {
- *t++ = *++p;
- if (prec || flags&ALT)
- *t++ = '.';
- /* if requires more precision and some integer left */
- for (; prec && ++p < endp; --prec)
- *t++ = *p;
- /*
- * if done precision and more of the integer component,
- * round using it; adjust fract so we don't re-round
- * later.
- */
- if (!prec && ++p < endp) {
- fract = 0;
- startp = round((double)0, &expcnt, startp,
- t - 1, *p, signp);
- }
- /* adjust expcnt for digit in front of decimal */
- --expcnt;
- }
- /* until first fractional digit, decrement exponent */
- else if (fract) {
- /* adjust expcnt for digit in front of decimal */
- for (expcnt = -1;; --expcnt) {
- fract = modf(fract * 10, &tmp);
- if (tmp)
- break;
- }
- *t++ = tochar((int)tmp);
- if (prec || flags&ALT)
- *t++ = '.';
- }
- else {
- *t++ = '0';
- if (prec || flags&ALT)
- *t++ = '.';
- }
- /* if requires more precision and some fraction left */
- if (fract) {
- if (prec)
- do {
- fract = modf(fract * 10, &tmp);
- *t++ = tochar((int)tmp);
- } while (--prec && fract);
- if (fract)
- startp = round(fract, &expcnt, startp,
- t - 1, (char)0, signp);
- }
- /* if requires more precision */
- for (; prec--; *t++ = '0');
-
- /* unless alternate flag, trim any g/G format trailing 0's */
- if (gformat && !(flags&ALT)) {
- while (t > startp && *--t == '0');
- if (*t == '.')
- --t;
- ++t;
- }
- t = exponent(t, expcnt, fmtch);
- break;
- case 'g':
- case 'G':
- /* a precision of 0 is treated as a precision of 1. */
- if (!prec)
- ++prec;
- /*
- * ``The style used depends on the value converted; style e
- * will be used only if the exponent resulting from the
- * conversion is less than -4 or greater than the precision.''
- * -- ANSI X3J11
- */
- if (expcnt > prec || !expcnt && fract && fract < .0001) {
- /*
- * g/G format counts "significant digits, not digits of
- * precision; for the e/E format, this just causes an
- * off-by-one problem, i.e. g/G considers the digit
- * before the decimal point significant and e/E doesn't
- * count it as precision.
- */
- --prec;
- fmtch -= 2; /* G->E, g->e */
- gformat = 1;
- goto eformat;
- }
- /*
- * reverse integer into beginning of buffer,
- * note, decrement precision
- */
- if (expcnt)
- for (; ++p < endp; *t++ = *p, --prec);
- else
- *t++ = '0';
- /*
- * if precision required or alternate flag set, add in a
- * decimal point. If no digits yet, add in leading 0.
- */
- if (prec || flags&ALT) {
- dotrim = 1;
- *t++ = '.';
- }
- else
- dotrim = 0;
- /* if requires more precision and some fraction left */
- if (fract) {
- if (prec) {
- do {
- fract = modf(fract * 10, &tmp);
- *t++ = tochar((int)tmp);
- } while(!tmp);
- while (--prec && fract) {
- fract = modf(fract * 10, &tmp);
- *t++ = tochar((int)tmp);
- }
- }
- if (fract)
- startp = round(fract, (int *)NULL, startp,
- t - 1, (char)0, signp);
- }
- /* alternate format, adds 0's for precision, else trim 0's */
- if (flags&ALT)
- for (; prec--; *t++ = '0');
- else if (dotrim) {
- while (t > startp && *--t == '0');
- if (*t != '.')
- ++t;
- }
- }
- return(t - startp);
- }
-
- static char *
- round(fract, exp, start, end, ch, signp)
- double fract;
- int *exp;
- register char *start, *end;
- char ch, *signp;
- {
- double tmp;
-
- if (fract)
- (void)modf(fract * 10, &tmp);
- else
- tmp = todigit(ch);
- if (tmp > 4)
- for (;; --end) {
- if (*end == '.')
- --end;
- if (++*end <= '9')
- break;
- *end = '0';
- if (end == start) {
- if (exp) { /* e/E; increment exponent */
- *end = '1';
- ++*exp;
- }
- else { /* f; add extra digit */
- *--end = '1';
- --start;
- }
- break;
- }
- }
- /* ``"%.3f", (double)-0.0004'' gives you a negative 0. */
- else if (*signp == '-')
- for (;; --end) {
- if (*end == '.')
- --end;
- if (*end != '0')
- break;
- if (end == start)
- *signp = 0;
- }
- return(start);
- }
-
- static char *
- exponent(p, exp, fmtch)
- register char *p;
- register int exp;
- u_char fmtch;
- {
- register char *t;
- char expbuf[MAXEXP];
-
- *p++ = fmtch;
- if (exp < 0) {
- exp = -exp;
- *p++ = '-';
- }
- else
- *p++ = '+';
- t = expbuf + MAXEXP;
- if (exp > 9) {
- do {
- *--t = tochar(exp % 10);
- } while ((exp /= 10) > 9);
- *--t = tochar(exp);
- for (; t < expbuf + MAXEXP; *p++ = *t++);
- }
- else {
- *p++ = '0';
- *p++ = tochar(exp);
- }
- return(p);
- }
-