891 lines
22 KiB
C
891 lines
22 KiB
C
/*
|
|
Copyright (C) 1993 Free Software Foundation
|
|
|
|
This file is part of the GNU IO Library. This library is free
|
|
software; you can redistribute it and/or modify it under the
|
|
terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this library; see the file COPYING. If not, write to the Free
|
|
Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
As a special exception, if you link this library with files
|
|
compiled with a GNU compiler to produce an executable, this does not cause
|
|
the resulting executable to be covered by the GNU General Public License.
|
|
This exception does not however invalidate any other reasons why
|
|
the executable file might be covered by the GNU General Public License. */
|
|
|
|
/*
|
|
* Copyright (c) 1990 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 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
|
|
#if defined(LIBC_SCCS) && !defined(lint)
|
|
static char sccsid[] = "%W% (Berkeley) %G%";
|
|
#endif /* LIBC_SCCS and not lint */
|
|
|
|
/*
|
|
* Actual printf innards.
|
|
*
|
|
* This code is large and complicated...
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include "libioP.h"
|
|
#include <string.h>
|
|
#ifdef __STDC__
|
|
#include <stdarg.h>
|
|
#else
|
|
#include <varargs.h>
|
|
#endif
|
|
|
|
#ifndef _IO_USE_DTOA
|
|
int __cvt_double __P((double number, register int prec, int flags, int *signp, int fmtch, char *startp, char *endp));
|
|
#endif
|
|
|
|
/*
|
|
* Define FLOATING_POINT to get floating point.
|
|
*/
|
|
#ifndef NO_FLOATING_POINT
|
|
#define FLOATING_POINT
|
|
#endif
|
|
|
|
/* end of configuration stuff */
|
|
|
|
|
|
/*
|
|
* Helper "class" for `fprintf to unbuffered': creates a
|
|
* temporary buffer. */
|
|
|
|
struct helper_file
|
|
{
|
|
struct _IO_FILE_plus _f;
|
|
_IO_FILE *_put_stream;
|
|
};
|
|
|
|
static int
|
|
_IO_helper_overflow (fp, c)
|
|
_IO_FILE *fp;
|
|
int c;
|
|
{
|
|
_IO_FILE *target = ((struct helper_file*)fp)->_put_stream;
|
|
int used = fp->_IO_write_ptr - fp->_IO_write_base;
|
|
if (used)
|
|
{
|
|
_IO_sputn(target, fp->_IO_write_base, used);
|
|
fp->_IO_write_ptr -= used;
|
|
}
|
|
return _IO_putc (c, fp);
|
|
}
|
|
|
|
static struct _IO_jump_t _IO_helper_jumps = {
|
|
JUMP_INIT_DUMMY,
|
|
JUMP_INIT(finish, _IO_default_finish),
|
|
JUMP_INIT(overflow, _IO_helper_overflow),
|
|
JUMP_INIT(underflow, _IO_default_underflow),
|
|
JUMP_INIT(uflow, _IO_default_uflow),
|
|
JUMP_INIT(pbackfail, _IO_default_pbackfail),
|
|
JUMP_INIT(xsputn, _IO_default_xsputn),
|
|
JUMP_INIT(xsgetn, _IO_default_xsgetn),
|
|
JUMP_INIT(seekoff, _IO_default_seekoff),
|
|
JUMP_INIT(seekpos, _IO_default_seekpos),
|
|
JUMP_INIT(setbuf, _IO_default_setbuf),
|
|
JUMP_INIT(sync, _IO_default_sync),
|
|
JUMP_INIT(doallocate, _IO_default_doallocate),
|
|
JUMP_INIT(read, _IO_default_read),
|
|
JUMP_INIT(write, _IO_default_write),
|
|
JUMP_INIT(seek, _IO_default_seek),
|
|
JUMP_INIT(close, _IO_default_close),
|
|
JUMP_INIT(stat, _IO_default_stat)
|
|
};
|
|
|
|
static int
|
|
helper_vfprintf (fp, fmt0, ap)
|
|
_IO_FILE *fp;
|
|
char const *fmt0;
|
|
_IO_va_list ap;
|
|
{
|
|
char buf[_IO_BUFSIZ];
|
|
struct helper_file helper;
|
|
register _IO_FILE *hp = (_IO_FILE*)&helper;
|
|
int result, to_flush;
|
|
|
|
/* initialize helper */
|
|
helper._put_stream = fp;
|
|
hp->_IO_write_base = buf;
|
|
hp->_IO_write_ptr = buf;
|
|
hp->_IO_write_end = buf+_IO_BUFSIZ;
|
|
hp->_IO_file_flags = _IO_MAGIC|_IO_NO_READS;
|
|
_IO_JUMPS(hp) = &_IO_helper_jumps;
|
|
|
|
/* Now print to helper instead. */
|
|
result = _IO_vfprintf(hp, fmt0, ap);
|
|
|
|
/* Now flush anything from the helper to the fp. */
|
|
if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0)
|
|
{
|
|
if (_IO_sputn(fp, hp->_IO_write_base, to_flush) != to_flush)
|
|
return EOF;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef FLOATING_POINT
|
|
|
|
#include "floatio.h"
|
|
#define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
|
|
#define DEFPREC 6
|
|
extern double modf __P((double, double*));
|
|
|
|
#else /* no FLOATING_POINT */
|
|
|
|
#define BUF 40
|
|
|
|
#endif /* FLOATING_POINT */
|
|
|
|
|
|
/*
|
|
* Macros for converting digits to letters and vice versa
|
|
*/
|
|
#define to_digit(c) ((c) - '0')
|
|
#define is_digit(c) ((unsigned)to_digit(c) <= 9)
|
|
#define to_char(n) ((n) + '0')
|
|
|
|
/*
|
|
* Flags used during conversion.
|
|
*/
|
|
#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 */
|
|
|
|
int
|
|
_IO_vfprintf (fp, fmt0, ap)
|
|
_IO_FILE *fp;
|
|
char const *fmt0;
|
|
_IO_va_list ap;
|
|
{
|
|
register const char *fmt; /* format string */
|
|
register int ch; /* character from fmt */
|
|
register int n; /* handy integer (short term usage) */
|
|
register char *cp; /* handy char pointer (short term usage) */
|
|
const char *fmark; /* for remembering a place in fmt */
|
|
register int flags; /* flags as above */
|
|
int ret; /* return value accumulator */
|
|
int width; /* width from format (%8d), or 0 */
|
|
int prec; /* precision from format (%.3d), or -1 */
|
|
char sign; /* sign prefix (' ', '+', '-', or \0) */
|
|
#ifdef FLOATING_POINT
|
|
int softsign; /* temporary negative sign for floats */
|
|
double _double; /* double precision arguments %[eEfgG] */
|
|
#ifndef _IO_USE_DTOA
|
|
int fpprec; /* `extra' floating precision in [eEfgG] */
|
|
#endif
|
|
#endif
|
|
unsigned long _ulong; /* integer arguments %[diouxX] */
|
|
enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
|
|
int dprec; /* a copy of prec if [diouxX], 0 otherwise */
|
|
int dpad; /* extra 0 padding needed for integers */
|
|
int fieldsz; /* field size expanded by sign, dpad etc */
|
|
/* The initialization of 'size' is to suppress a warning that
|
|
'size' might be used unitialized. It seems gcc can't
|
|
quite grok this spaghetti code ... */
|
|
int size = 0; /* size of converted field or string */
|
|
char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */
|
|
char ox[2]; /* space for 0x hex-prefix */
|
|
|
|
/*
|
|
* BEWARE, these `goto error' on error, and PAD uses `n'.
|
|
*/
|
|
#define PRINT(ptr, len) \
|
|
do { if (_IO_sputn(fp,ptr, len) != len) goto error; } while (0)
|
|
#define PAD_SP(howmany) if (_IO_padn(fp, ' ', howmany) < (howmany)) goto error;
|
|
#define PAD_0(howmany) if (_IO_padn(fp, '0', howmany) < (howmany)) goto error;
|
|
|
|
/*
|
|
* To extend shorts properly, we need both signed and unsigned
|
|
* argument extraction methods.
|
|
*/
|
|
#define SARG() \
|
|
(flags&LONGINT ? va_arg(ap, long) : \
|
|
flags&SHORTINT ? (long)(short)va_arg(ap, int) : \
|
|
(long)va_arg(ap, int))
|
|
#define UARG() \
|
|
(flags&LONGINT ? va_arg(ap, unsigned long) : \
|
|
flags&SHORTINT ? (unsigned long)(unsigned short)va_arg(ap, int) : \
|
|
(unsigned long)va_arg(ap, unsigned int))
|
|
|
|
/* optimise stderr (and other unbuffered Unix files) */
|
|
if (fp->_IO_file_flags & _IO_UNBUFFERED)
|
|
return helper_vfprintf(fp, fmt0, ap);
|
|
|
|
fmt = fmt0;
|
|
ret = 0;
|
|
|
|
/*
|
|
* Scan the format for conversions (`%' character).
|
|
*/
|
|
for (;;) {
|
|
for (fmark = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
|
|
/* void */;
|
|
if ((n = fmt - fmark) != 0) {
|
|
PRINT(fmark, n);
|
|
ret += n;
|
|
}
|
|
if (ch == '\0')
|
|
goto done;
|
|
fmt++; /* skip over '%' */
|
|
|
|
flags = 0;
|
|
dprec = 0;
|
|
#if defined(FLOATING_POINT) && !defined (_IO_USE_DTOA)
|
|
fpprec = 0;
|
|
#endif
|
|
width = 0;
|
|
prec = -1;
|
|
sign = '\0';
|
|
|
|
rflag: ch = *fmt++;
|
|
reswitch: switch (ch) {
|
|
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(ap, int)) >= 0)
|
|
goto rflag;
|
|
width = -width;
|
|
/* FALLTHROUGH */
|
|
case '-':
|
|
flags |= LADJUST;
|
|
flags &= ~ZEROPAD; /* '-' disables '0' */
|
|
goto rflag;
|
|
case '+':
|
|
sign = '+';
|
|
goto rflag;
|
|
case '.':
|
|
if ((ch = *fmt++) == '*') {
|
|
n = va_arg(ap, int);
|
|
prec = n < 0 ? -1 : n;
|
|
goto rflag;
|
|
}
|
|
n = 0;
|
|
while (is_digit(ch)) {
|
|
n = 10 * n + to_digit(ch);
|
|
ch = *fmt++;
|
|
}
|
|
prec = n < 0 ? -1 : n;
|
|
goto reswitch;
|
|
case '0':
|
|
/*
|
|
* ``Note that 0 is taken as a flag, not as the
|
|
* beginning of a field width.''
|
|
* -- ANSI X3J11
|
|
*/
|
|
if (!(flags & LADJUST))
|
|
flags |= ZEROPAD; /* '-' disables '0' */
|
|
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 + to_digit(ch);
|
|
ch = *fmt++;
|
|
} while (is_digit(ch));
|
|
width = n;
|
|
goto reswitch;
|
|
#ifdef FLOATING_POINT
|
|
case 'L':
|
|
flags |= LONGDBL;
|
|
goto rflag;
|
|
#endif
|
|
case 'h':
|
|
flags |= SHORTINT;
|
|
goto rflag;
|
|
case 'l':
|
|
flags |= LONGINT;
|
|
goto rflag;
|
|
case 'c':
|
|
*(cp = buf) = va_arg(ap, int);
|
|
size = 1;
|
|
sign = '\0';
|
|
break;
|
|
case 'D':
|
|
flags |= LONGINT;
|
|
/*FALLTHROUGH*/
|
|
case 'd':
|
|
case 'i':
|
|
_ulong = SARG();
|
|
if ((long)_ulong < 0) {
|
|
_ulong = -_ulong;
|
|
sign = '-';
|
|
}
|
|
base = DEC;
|
|
goto number;
|
|
#ifdef FLOATING_POINT
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G':
|
|
_double = va_arg(ap, double);
|
|
#ifdef _IO_USE_DTOA
|
|
{
|
|
int fmt_flags = 0;
|
|
int fill = ' ';
|
|
if (flags & ALT)
|
|
fmt_flags |= _IO_SHOWPOINT;
|
|
if (flags & LADJUST)
|
|
fmt_flags |= _IO_LEFT;
|
|
else if (flags & ZEROPAD)
|
|
fmt_flags |= _IO_INTERNAL, fill = '0';
|
|
n = _IO_outfloat(_double, fp, ch, width,
|
|
prec < 0 ? DEFPREC : prec,
|
|
fmt_flags, sign, fill);
|
|
if (n < 0)
|
|
goto error;
|
|
ret += n;
|
|
}
|
|
/* CHECK ERROR! */
|
|
continue;
|
|
#else
|
|
/*
|
|
* don't do unrealistic precision; just pad it with
|
|
* zeroes later, so buffer size stays rational.
|
|
*/
|
|
if (prec > MAXFRACT) {
|
|
if ((ch != 'g' && ch != 'G') || (flags&ALT))
|
|
fpprec = prec - MAXFRACT;
|
|
prec = MAXFRACT;
|
|
} else if (prec == -1)
|
|
prec = DEFPREC;
|
|
/* __cvt_double may have to round up before the
|
|
"start" of its buffer, i.e.
|
|
``intf("%.2f", (double)9.999);'';
|
|
if the first character is still NUL, it did.
|
|
softsign avoids negative 0 if _double < 0 but
|
|
no significant digits will be shown. */
|
|
cp = buf;
|
|
*cp = '\0';
|
|
size = __cvt_double(_double, prec, flags, &softsign,
|
|
ch, cp, buf + sizeof(buf));
|
|
if (softsign)
|
|
sign = '-';
|
|
if (*cp == '\0')
|
|
cp++;
|
|
break;
|
|
#endif
|
|
#endif /* FLOATING_POINT */
|
|
case 'n':
|
|
if (flags & LONGINT)
|
|
*va_arg(ap, long *) = ret;
|
|
else if (flags & SHORTINT)
|
|
*va_arg(ap, short *) = ret;
|
|
else
|
|
*va_arg(ap, int *) = ret;
|
|
continue; /* no output */
|
|
case 'O':
|
|
flags |= LONGINT;
|
|
/*FALLTHROUGH*/
|
|
case 'o':
|
|
_ulong = UARG();
|
|
base = OCT;
|
|
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 = (unsigned long)va_arg(ap, void *);
|
|
base = HEX;
|
|
flags |= HEXPREFIX;
|
|
ch = 'x';
|
|
goto nosign;
|
|
case 's':
|
|
if ((cp = va_arg(ap, char *)) == NULL)
|
|
cp = "(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 = (char*)memchr(cp, 0, prec);
|
|
|
|
if (p != NULL) {
|
|
size = p - cp;
|
|
if (size > prec)
|
|
size = prec;
|
|
} else
|
|
size = prec;
|
|
} else
|
|
size = strlen(cp);
|
|
sign = '\0';
|
|
break;
|
|
case 'U':
|
|
flags |= LONGINT;
|
|
/*FALLTHROUGH*/
|
|
case 'u':
|
|
_ulong = UARG();
|
|
base = DEC;
|
|
goto nosign;
|
|
case 'X':
|
|
case 'x':
|
|
_ulong = UARG();
|
|
base = HEX;
|
|
/* 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
|
|
*/
|
|
cp = buf + BUF;
|
|
if (_ulong != 0 || prec != 0) {
|
|
char *xdigs; /* digits for [xX] conversion */
|
|
/*
|
|
* unsigned mod is hard, and unsigned mod
|
|
* by a constant is easier than that by
|
|
* a variable; hence this switch.
|
|
*/
|
|
switch (base) {
|
|
case OCT:
|
|
do {
|
|
*--cp = to_char(_ulong & 7);
|
|
_ulong >>= 3;
|
|
} while (_ulong);
|
|
/* handle octal leading 0 */
|
|
if (flags & ALT && *cp != '0')
|
|
*--cp = '0';
|
|
break;
|
|
|
|
case DEC:
|
|
/* many numbers are 1 digit */
|
|
while (_ulong >= 10) {
|
|
*--cp = to_char(_ulong % 10);
|
|
_ulong /= 10;
|
|
}
|
|
*--cp = to_char(_ulong);
|
|
break;
|
|
|
|
case HEX:
|
|
if (ch == 'X')
|
|
xdigs = "0123456789ABCDEF";
|
|
else /* ch == 'x' || ch == 'p' */
|
|
xdigs = "0123456789abcdef";
|
|
do {
|
|
*--cp = xdigs[_ulong & 15];
|
|
_ulong >>= 4;
|
|
} while (_ulong);
|
|
break;
|
|
|
|
default:
|
|
cp = "bug in vform: bad base";
|
|
goto skipsize;
|
|
}
|
|
}
|
|
size = buf + BUF - cp;
|
|
skipsize:
|
|
break;
|
|
default: /* "%?" prints ?, unless ? is NUL */
|
|
if (ch == '\0')
|
|
goto done;
|
|
/* pretend it was %c with argument ch */
|
|
cp = buf;
|
|
*cp = ch;
|
|
size = 1;
|
|
sign = '\0';
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* All reasonable formats wind up here. At this point,
|
|
* `cp' 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.
|
|
*/
|
|
#if defined(FLOATING_POINT) && !defined (_IO_USE_DTOA)
|
|
fieldsz = size + fpprec;
|
|
#else
|
|
fieldsz = size;
|
|
#endif
|
|
dpad = dprec - size;
|
|
if (dpad < 0)
|
|
dpad = 0;
|
|
|
|
if (sign)
|
|
fieldsz++;
|
|
else if (flags & HEXPREFIX)
|
|
fieldsz += 2;
|
|
fieldsz += dpad;
|
|
|
|
/* right-adjusting blank padding */
|
|
if ((flags & (LADJUST|ZEROPAD)) == 0)
|
|
PAD_SP(width - fieldsz);
|
|
|
|
/* prefix */
|
|
if (sign) {
|
|
PRINT(&sign, 1);
|
|
} else if (flags & HEXPREFIX) {
|
|
ox[0] = '0';
|
|
ox[1] = ch;
|
|
PRINT(ox, 2);
|
|
}
|
|
|
|
/* right-adjusting zero padding */
|
|
if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
|
|
PAD_0(width - fieldsz);
|
|
|
|
/* leading zeroes from decimal precision */
|
|
PAD_0(dpad);
|
|
|
|
/* the string or number proper */
|
|
PRINT(cp, size);
|
|
|
|
#if defined(FLOATING_POINT) && !defined (_IO_USE_DTOA)
|
|
/* trailing f.p. zeroes */
|
|
PAD_0(fpprec);
|
|
#endif
|
|
|
|
/* left-adjusting padding (always blank) */
|
|
if (flags & LADJUST)
|
|
PAD_SP(width - fieldsz);
|
|
|
|
/* finally, adjust ret */
|
|
ret += width > fieldsz ? width : fieldsz;
|
|
|
|
}
|
|
done:
|
|
return ret;
|
|
error:
|
|
return EOF;
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
#if defined(FLOATING_POINT) && !defined(_IO_USE_DTOA)
|
|
|
|
static char *exponent(register char *p, register int exp, int 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 = to_char(exp % 10);
|
|
} while ((exp /= 10) > 9);
|
|
*--t = to_char(exp);
|
|
for (; t < expbuf + MAXEXP; *p++ = *t++);
|
|
}
|
|
else {
|
|
*p++ = '0';
|
|
*p++ = to_char(exp);
|
|
}
|
|
return (p);
|
|
}
|
|
|
|
static char * round(double fract, int *exp,
|
|
register char *start, register char *end,
|
|
char ch, int *signp)
|
|
{
|
|
double tmp;
|
|
|
|
if (fract)
|
|
(void)modf(fract * 10, &tmp);
|
|
else
|
|
tmp = to_digit(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);
|
|
}
|
|
|
|
int __cvt_double(double number, register int prec, int flags, int *signp,
|
|
int fmtch, char *startp, char *endp)
|
|
{
|
|
register char *p, *t;
|
|
register double fract;
|
|
int dotrim = 0, expcnt, gformat = 0;
|
|
double integer, tmp;
|
|
|
|
expcnt = 0;
|
|
if (number < 0) {
|
|
number = -number;
|
|
*signp = '-';
|
|
} else
|
|
*signp = 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; p >= startp && integer; ++expcnt) {
|
|
tmp = modf(integer / 10, &integer);
|
|
*p-- = to_char((int)((tmp + .01) * 10));
|
|
}
|
|
switch (fmtch) {
|
|
case 'f':
|
|
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++ = to_char((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++ = to_char((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++ = to_char((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) {
|
|
/* If no integer part, don't count initial
|
|
* zeros as significant digits. */
|
|
do {
|
|
fract = modf(fract * 10, &tmp);
|
|
*t++ = to_char((int)tmp);
|
|
} while(!tmp && !expcnt);
|
|
while (--prec && fract) {
|
|
fract = modf(fract * 10, &tmp);
|
|
*t++ = to_char((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);
|
|
}
|
|
|
|
#endif /* defined(FLOATING_POINT) && !defined(_IO_USE_DTOA) */
|