must be sure to always convert b_bno to disk block (db) units
[unix-history] / usr / src / lib / libc / stdio / vfprintf.c
index 7d86521..ae33134 100644 (file)
@@ -11,7 +11,7 @@
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)vfprintf.c 5.24 (Berkeley) %G%";
+static char sccsid[] = "@(#)vfprintf.c 5.33 (Berkeley) %G%";
 #endif /* LIBC_SCCS and not lint */
 
 #include <sys/types.h>
 #endif /* LIBC_SCCS and not lint */
 
 #include <sys/types.h>
@@ -24,14 +24,19 @@ static char sccsid[] = "@(#)vfprintf.c      5.24 (Berkeley) %G%";
 /* 128 bit fraction takes up 39 decimal digits; max reasonable precision */
 #define        MAXFRACT        39
 
 /* 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        BUF             (MAXEXP+MAXFRACT+1)     /* + decimal point */
 
-#define        PUTC(ch)        {++cnt; putc((char)ch, fp);}
+#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        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
 
 /* have to deal with the negative buffer count kludge */
 #define        NEGATIVE_COUNT_KLUDGE
 
@@ -40,51 +45,78 @@ static char sccsid[] = "@(#)vfprintf.c      5.24 (Berkeley) %G%";
 #define        SHORTINT        0x04            /* short integer */
 #define        ALT             0x08            /* alternate form */
 #define        LADJUST         0x10            /* left adjustment */
 #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 */
 
 _doprnt(fmt0, argp, fp)
        u_char *fmt0;
        va_list argp;
        register FILE *fp;
 {
 
 _doprnt(fmt0, argp, fp)
        u_char *fmt0;
        va_list argp;
        register FILE *fp;
 {
-       register u_char *fmt;
-       register int ch, cnt, n;
-       register char *t;
-       double _double;
-       u_long _ulong;
-       int base, flags, fpprec, prec, size, width;
-       char padc, sign, *digs, buf[BUF], *_cvt();
+       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;
 
        fmt = fmt0;
        digs = "0123456789abcdef";
        for (cnt = 0;; ++fmt) {
                n = fp->_cnt;
-               for (t = fp->_ptr; (ch = *fmt) && ch != '%'; ++cnt, ++fmt)
+               for (t = (char *)fp->_ptr; (ch = *fmt) && ch != '%';
+                    ++cnt, ++fmt)
                        if (--n < 0
 #ifdef NEGATIVE_COUNT_KLUDGE
                            && (!(fp->_flag & _IOLBF) || -n >= fp->_bufsiz)
 #endif
                        if (--n < 0
 #ifdef NEGATIVE_COUNT_KLUDGE
                            && (!(fp->_flag & _IOLBF) || -n >= fp->_bufsiz)
 #endif
-                           || ch == '\n' && fp->_flag&_IOLBF) {
+                           || ch == '\n' && fp->_flag & _IOLBF) {
                                fp->_cnt = n;
                                fp->_ptr = t;
                                fp->_cnt = n;
                                fp->_ptr = t;
-                               (void)_flsbuf(ch, fp);
+                               (void) _flsbuf((u_char)ch, fp);
                                n = fp->_cnt;
                                n = fp->_cnt;
-                               t = fp->_ptr;
-                       }
-                       else
+                               t = (char *)fp->_ptr;
+                       } else
                                *t++ = ch;
                fp->_cnt = n;
                fp->_ptr = t;
                if (!ch)
                                *t++ = ch;
                fp->_cnt = n;
                fp->_ptr = t;
                if (!ch)
-                       return(cnt);
+                       return (cnt);
 
 
-               flags = fpprec = width = 0;
+               flags = dprec = fpprec = width = 0;
                prec = -1;
                prec = -1;
-               padc = ' ';
                sign = '\0';
 
 rflag:         switch (*++fmt) {
                case ' ':
                sign = '\0';
 
 rflag:         switch (*++fmt) {
                case ' ':
-                       sign = ' ';
+                       /*
+                        * ``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 '#':
                        flags |= ALT;
@@ -99,7 +131,7 @@ rflag:               switch (*++fmt) {
                        if ((width = va_arg(argp, int)) >= 0)
                                goto rflag;
                        width = -width;
                        if ((width = va_arg(argp, int)) >= 0)
                                goto rflag;
                        width = -width;
-                       /*FALLTHROUGH*/
+                       /* FALLTHROUGH */
                case '-':
                        flags |= LADJUST;
                        goto rflag;
                case '-':
                        flags |= LADJUST;
                        goto rflag;
@@ -109,38 +141,33 @@ rflag:            switch (*++fmt) {
                case '.':
                        if (*++fmt == '*')
                                n = va_arg(argp, int);
                case '.':
                        if (*++fmt == '*')
                                n = va_arg(argp, int);
-                       else if (isascii(*fmt) && isdigit(*fmt)) {
-                               n = 0;
-                               do {
-                                       n = 10 * n + *fmt - '0';
-                               } while (isascii(*++fmt) && isdigit(*fmt));
-                               --fmt;
-                       }
                        else {
                        else {
+                               n = 0;
+                               while (isascii(*fmt) && isdigit(*fmt))
+                                       n = 10 * n + todigit(*fmt++);
                                --fmt;
                                --fmt;
-                               prec = 0;
-                               goto rflag;
                        }
                        prec = n < 0 ? -1 : n;
                        goto rflag;
                case '0':
                        }
                        prec = n < 0 ? -1 : n;
                        goto rflag;
                case '0':
-                       padc = '0';
-                       /*FALLTHROUGH*/
+                       /*
+                        * ``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 {
                case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                        n = 0;
                        do {
-                               n = 10 * n + *fmt - '0';
+                               n = 10 * n + todigit(*fmt);
                        } while (isascii(*++fmt) && isdigit(*fmt));
                        width = n;
                        --fmt;
                        goto rflag;
                case 'L':
                        } while (isascii(*++fmt) && isdigit(*fmt));
                        width = n;
                        --fmt;
                        goto rflag;
                case 'L':
-                       /*
-                        * C doesn't have a long double; use long for now.
-                        * flags |= LONGDBL;
-                        */
-                       flags |= LONGINT;
+                       flags |= LONGDBL;
                        goto rflag;
                case 'h':
                        flags |= SHORTINT;
                        goto rflag;
                case 'h':
                        flags |= SHORTINT;
@@ -149,10 +176,13 @@ rflag:            switch (*++fmt) {
                        flags |= LONGINT;
                        goto rflag;
                case 'c':
                        flags |= LONGINT;
                        goto rflag;
                case 'c':
-                       buf[0] = va_arg(argp, int);
+                       *(t = buf) = va_arg(argp, int);
                        size = 1;
                        size = 1;
-                       t = buf;
+                       sign = '\0';
                        goto pforw;
                        goto pforw;
+               case 'D':
+                       flags |= LONGINT;
+                       /*FALLTHROUGH*/
                case 'd':
                case 'i':
                        ARG();
                case 'd':
                case 'i':
                        ARG();
@@ -169,34 +199,49 @@ rflag:            switch (*++fmt) {
                case 'G':
                        _double = va_arg(argp, double);
                        /*
                case 'G':
                        _double = va_arg(argp, double);
                        /*
-                        * don't bother to do unrealistic precision; just
-                        * pad it with zeroes later.  This keeps buffer size
-                        * rational.
+                        * don't do unrealistic precision; just pad it with
+                        * zeroes later, so buffer size stays rational.
                         */
                        if (prec > MAXFRACT) {
                         */
                        if (prec > MAXFRACT) {
-                               fpprec = prec - MAXFRACT;
+                               if (*fmt != 'g' && *fmt != 'G' || (flags&ALT))
+                                       fpprec = prec - MAXFRACT;
                                prec = MAXFRACT;
                        }
                                prec = MAXFRACT;
                        }
-                       size = _cvt(_double, prec, flags, *fmt, padc, &sign,
-                           buf, buf + sizeof(buf)) - buf;
-                       t = buf;
+                       else if (prec == -1)
+                               prec = DEFPREC;
                        /*
                        /*
-                        * zero-padded sign put out here; blank padded sign
-                        * placed in number in _cvt().
+                        * softsign avoids negative 0 if _double is < 0 and
+                        * no significant digits will be shown
                         */
                         */
-                       if (sign && padc == '0') {
-                               PUTC(sign);
-                               --width;
+                       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':
                        goto pforw;
                case 'n':
-                       if (flags&LONGDBL || flags&LONGINT)
+                       if (flags & LONGINT)
                                *va_arg(argp, long *) = cnt;
                                *va_arg(argp, long *) = cnt;
-                       else if (flags&SHORTINT)
+                       else if (flags & SHORTINT)
                                *va_arg(argp, short *) = cnt;
                        else
                                *va_arg(argp, int *) = cnt;
                        break;
                                *va_arg(argp, short *) = cnt;
                        else
                                *va_arg(argp, int *) = cnt;
                        break;
+               case 'O':
+                       flags |= LONGINT;
+                       /*FALLTHROUGH*/
                case 'o':
                        ARG();
                        base = 8;
                case 'o':
                        ARG();
                        base = 8;
@@ -209,7 +254,7 @@ rflag:              switch (*++fmt) {
                         * defined manner.''
                         *      -- ANSI X3J11
                         */
                         * defined manner.''
                         *      -- ANSI X3J11
                         */
-                       /*NOSTRICT*/
+                       /* NOSTRICT */
                        _ulong = (u_long)va_arg(argp, void *);
                        base = 16;
                        goto nosign;
                        _ulong = (u_long)va_arg(argp, void *);
                        base = 16;
                        goto nosign;
@@ -228,340 +273,387 @@ rflag:          switch (*++fmt) {
                                        size = p - t;
                                        if (size > prec)
                                                size = prec;
                                        size = p - t;
                                        if (size > prec)
                                                size = prec;
-                               }
-                               else
+                               } else
                                        size = prec;
                                        size = prec;
-                       }
-                       else
+                       } else
                                size = strlen(t);
                                size = strlen(t);
+                       sign = '\0';
                        goto pforw;
                        goto pforw;
+               case 'U':
+                       flags |= LONGINT;
+                       /*FALLTHROUGH*/
                case 'u':
                        ARG();
                        base = 10;
                        goto nosign;
                case 'X':
                        digs = "0123456789ABCDEF";
                case 'u':
                        ARG();
                        base = 10;
                        goto nosign;
                case 'X':
                        digs = "0123456789ABCDEF";
-                       /*FALLTHROUGH*/
+                       /* FALLTHROUGH */
                case 'x':
                        ARG();
                        base = 16;
                        /* leading 0x/X only if non-zero */
                case 'x':
                        ARG();
                        base = 16;
                        /* leading 0x/X only if non-zero */
-                       if (!_ulong)
-                               flags &= ~ALT;
+                       if (flags & ALT && _ulong != 0)
+                               flags |= HEXPREFIX;
 
                        /* unsigned conversions */
 
                        /* unsigned conversions */
-nosign:                        sign = NULL;
+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
                         */
                        /*
                         * ``The result of converting a zero value with an
                         * explicit precision of zero is no characters.''
                         *      -- ANSI X3J11
                         */
-number:                        if (!_ulong && !prec)
-                               break;
-
-                       t = buf + BUF - 1;
-                       do {
-                               *t-- = digs[_ulong % base];
-                               _ulong /= base;
-                       } while(_ulong);
-                       for (size = buf + BUF - 1 - t; size < prec; ++size)
-                               *t-- = '0';
-                       digs = "0123456789abcdef";
+                       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;
 
 
-                       /* alternate mode for hex and octal numbers */
-                       if (flags&ALT)
-                               switch (base) {
-                               case 16:
-                                       /* avoid "00000x35" */
-                                       if (padc == ' ') {
-                                               *t-- = *fmt;
-                                               *t-- = '0';
-                                               size += 2;
-                                       }
-                                       else {
-                                               PUTC('0');
-                                               PUTC(*fmt);
-                                               width -= 2;
-                                       }
-                                       break;
-                               case 8:
-                                       if (t[1] != '0') {
-                                               *t-- = '0';
-                                               ++size;
-                                       }
-                                       break;
-                               }
+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.
+                        */
 
 
-                       if (sign) {
-                               /* avoid "0000-3" */
-                               if (padc == ' ') {
-                                       *t-- = sign;
-                                       ++size;
-                               }
-                               else {
-                                       PUTC(sign);
-                                       --width;
-                               }
+                       /*
+                        * 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);
                        }
                        }
-                       ++t;
+                       /* 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');
 
 
-pforw:                 if (!(flags&LADJUST) && width)
-                               for (n = size + fpprec; n++ < width;)
-                                       PUTC(padc);
-                       if (fp->_cnt - (n = size) >= 0) {
-                               cnt += n;
+                       /* the string or number proper */
+                       if (fp->_cnt - (n = size) >= 0 &&
+                           (fp->_flag & _IOLBF) == 0) {
                                fp->_cnt -= n;
                                fp->_cnt -= n;
-                               bcopy(t, fp->_ptr, n);
+                               bcopy(t, (char *)fp->_ptr, n);
                                fp->_ptr += n;
                                fp->_ptr += n;
-                       }
-                       else for (; n--; ++t)
-                               PUTC(*t);
-                       while (fpprec--)
+                       } else
+                               while (--n >= 0)
+                                       PUTC(*t++);
+                       /* trailing f.p. zeroes */
+                       while (--fpprec >= 0)
                                PUTC('0');
                                PUTC('0');
-                       if (flags&LADJUST)
-                               for (n = size + fpprec; ++n < width;)
+                       /* left-adjusting padding (always blank) */
+                       if (flags & LADJUST)
+                               for (n = realsz; n < width; n++)
                                        PUTC(' ');
                                        PUTC(' ');
+                       /* finally, adjust cnt */
+                       cnt += width > realsz ? width : realsz;
                        break;
                        break;
-               case '\0':              /* "%?" prints ?, unless ? is NULL */
-                       return(cnt);
+               case '\0':      /* "%?" prints ?, unless ? is NULL */
+                       return (cnt);
                default:
                default:
-                       PUTC(*fmt);
+                       PUTC((char)*fmt);
+                       cnt++;
                }
        }
                }
        }
-       /*NOTREACHED*/
+       /* NOTREACHED */
 }
 
 }
 
-#define        EFORMAT 0x01
-#define        FFORMAT 0x02
-#define        GFORMAT 0x04
-#define        DEFPREC 6
-
-static char *
-_cvt(number, prec, flags, fmtch, padc, sign, startp, endp)
+static
+cvt(number, prec, flags, signp, fmtch, startp, endp)
        double number;
        register int prec;
        int flags;
        u_char fmtch;
        double number;
        register int prec;
        int flags;
        u_char fmtch;
-       char padc, *sign, *startp, *endp;
+       char *signp, *startp, *endp;
 {
 {
-       register char *p;
-       register int expcnt, format;
+       register char *p, *t;
        double fract, integer, tmp, modf();
        double fract, integer, tmp, modf();
-       int decpt;
-       char *savep;
+       int dotrim, expcnt, gformat;
+       char *exponent(), *round();
 
 
-       if (prec == -1)
-               prec = DEFPREC;
-
-       if (number < 0) {
-               *sign = '-';
-               number = -number;
-       }
+       dotrim = expcnt = gformat = 0;
+       fract = modf(number, &integer);
 
 
-       /* if blank padded, add sign in as part of the number */
-       if (*sign && padc == ' ')
-               *startp++ = *sign;
+       /* 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) {
        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':
        case 'e':
        case 'E':
-               format = EFORMAT;
-               break;
-       case 'f':
-               format = FFORMAT;
+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':
                break;
        case 'g':
        case 'G':
-               format = GFORMAT;
-               fmtch -= 2;
-       }
-
-       /*
-        * if the alternate flag is set, or, at least one digit of precision
-        * was requested, add a decimal point, unless it's the g/G format
-        * in which case we require two digits of precision, as it counts
-        * precision differently.
-        */
-       decpt = flags&ALT || prec > (format&GFORMAT ? 1 : 0);
-
-       expcnt = 0;
-       p = endp - 1;
-       fract = modf(number, &integer);
-       if (integer) {
-               register char *p2;
-
-               /* get integer part of number; count decimal places */
-               for (; integer; ++expcnt) {
-                       tmp = modf(integer / 10, &integer);
-                       *p-- = (int)((tmp + .03) * 10) + '0';
-               }
-
-               /* copy, in reverse order, to start of buffer */
-               p2 = startp;
-               *p2++ = *++p;
-
+               /* a precision of 0 is treated as a precision of 1. */
+               if (!prec)
+                       ++prec;
                /*
                /*
-                * if the format is g/G, and the resulting exponent will be
-                * greater than the precision, use e/E format.  If e/E format,
-                * put in a decimal point as needed, and decrement precision
-                * count for each digit after the decimal point.
+                * ``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 (format&GFORMAT && expcnt - 1 > prec || format&EFORMAT) {
-                       if (format&GFORMAT) {
-                               format |= EFORMAT;
-
-                               /* first digit is precision for g/G format */
-                               if (prec)
-                                       --prec;
-                       }
-                       if (decpt)
-                               *p2++ = '.';
-                       for (; ++p < endp && prec; --prec, *p2++ = *p);
-
-                       /* precision ran out, round */
-                       if (p < endp) {
-                               if (*p > '4') {
-                                       for (savep = p2--;; *p2-- = '0') {
-                                               if (*p2 == '.')
-                                                       --p2;
-                                               if (++*p2 <= '9')
-                                                       break;
-                                       }
-                                       p2 = savep;
-                               }
-                               fract = 0;
-                       }
+               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;
                }
                /*
                }
                /*
-                * g/G in f format; if out of precision, replace digits with
-                * zeroes, note, have to round first.
+                * reverse integer into beginning of buffer,
+                * note, decrement precision
                 */
                 */
-               else if (format&GFORMAT) {
-                       for (; ++p < endp && prec; --prec, *p2++ = *p);
-                       /* precision ran out; round and then add zeroes */
-                       if (p < endp) {
-                               if (*p > '4') {
-                                       for (savep = p2--; ++*p2 > '9';
-                                           *p2-- = '0');
-                                       p2 = savep;
-                               }
+               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 {
                                do {
-                                       *p2++ = '0';
-                               } while (++p < endp);
-                               fract = 0;
+                                       fract = modf(fract * 10, &tmp);
+                                       *t++ = tochar((int)tmp);
+                               } while(!tmp);
+                               while (--prec && fract) {
+                                       fract = modf(fract * 10, &tmp);
+                                       *t++ = tochar((int)tmp);
+                               }
                        }
                        }
-                       if (decpt)
-                               *p2++ = '.';
+                       if (fract)
+                               startp = round(fract, (int *)NULL, startp,
+                                   t - 1, (char)0, signp);
                }
                }
-               /* f format */
-               else {
-                       for (; ++p < endp; *p2++ = *p);
-                       if (decpt)
-                               *p2++ = '.';
+               /* 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;
                }
                }
-               p = p2;
-       }
-       /*
-        * if no fraction, the number was zero, and if no precision, can't
-        * show anything after the decimal point.
-        */
-       else if (!fract || !prec) {
-               *startp++ = '0';
-               if (decpt && !(format&GFORMAT))
-                       *startp++ = '.';
-               *startp = '\0';
-               return(startp);
-       }
-       /*
-        * if the format is g/G, and the resulting exponent will be less than
-        * -4 use e/E format.  If e/E format, compute exponent value.
-        */
-       else if (format&GFORMAT && fract < .0001 || format&EFORMAT) {
-               format |= EFORMAT;
-               if (fract)
-                       for (p = startp; fract;) {
-                               fract = modf(fract * 10, &tmp);
-                               if (!tmp) {
-                                       --expcnt;
-                                       continue;
-                               }
-                               *p++ = (int)tmp + '0';
-                               break;
-                       }
-               else
-                       *p++ = '0';
-
-               /* g/G format, decrement precision for first digit */
-               if (format&GFORMAT && prec)
-                       --prec;
-
-               /* add decimal after first non-zero digit */
-               if (decpt)
-                       *p++ = '.';
-       }
-       /*
-        * f format or g/G printed as f format; don't worry about decimal
-        * point, if g/G format doesn't need it, will get stripped later.
-        */
-       else {
-               p = startp;
-               *p++ = '0';
-               *p++ = '.';
        }
        }
+       return(t - startp);
+}
 
 
-       /* finish out requested precision */
-       while (fract && prec-- > 0) {
-               fract = modf(fract * 10, &tmp);
-               *p++ = (int)tmp + '0';
-       }
-       while (prec-- > 0)
-               *p++ = '0';
+static char *
+round(fract, exp, start, end, ch, signp)
+       double fract;
+       int *exp;
+       register char *start, *end;
+       char ch, *signp;
+{
+       double tmp;
 
 
-       /*
-        * if any fractional value left, "round" it back up to the beginning
-        * of the number, fixing the exponent as necessary, and avoiding the
-        * decimal point.
-        */
-       if (fract) {
+       if (fract)
                (void)modf(fract * 10, &tmp);
                (void)modf(fract * 10, &tmp);
-               if (tmp > 4) {
-                       for (savep = p--;; *p-- = '0') {
-                               if (*p == '.')
-                                       --p;
-                               if (p == startp) {
-                                       *p = '1';
-                                       ++expcnt;
-                                       break;
+       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;
                                }
                                }
-                               if (++*p <= '9')
-                                       break;
+                               else {          /* f; add extra digit */
+                                       *--end = '1';
+                                       --start;
+                               }
+                               break;
                        }
                        }
-                       p = savep;
                }
                }
-       }
+       /* ``"%.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);
+}
 
 
-       /*
-        * if a g/G format and not alternate flag, lose trailing zeroes,
-        * if e/E or g/G format, and last char is decimal point, lose it.
-        */
-       if (!(flags&ALT)) {
-               if (format&GFORMAT)
-                       for (; p[-1] == '0'; --p);
-               if (format&(GFORMAT|EFORMAT) && p[-1] == '.')
-                       --p;
-       }
+static char *
+exponent(p, exp, fmtch)
+       register char *p;
+       register int exp;
+       u_char fmtch;
+{
+       register char *t;
+       char expbuf[MAXEXP];
 
 
-       /* if an e/E format, add exponent */
-       if (format&EFORMAT) {
-               *p++ = fmtch;
-               if (--expcnt < 0) {
-                       expcnt = -expcnt;
-                       *p++ = '-';
-               }
-               else
-                       *p++ = '+';
-               *p++ = expcnt / 10 + '0';
-               *p++ = expcnt % 10 + '0';
+       *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);
 }
        }
        return(p);
 }