Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_FS_1_19910112 - c/lib/stdio/printf.c
There are 9 other files named printf.c in the archive. Click here to see a list.
/*
**	PRINTF - formatted output conversion.
**
**	(c) Copyright Ken Harrenstien 1989
**		for all changes after v.27, 12-Apr-1988
**	(c) Copyright Ken Harrenstien, SRI International 1985
**
*/
/* This version has been written to conform to the description of
 * printf in the CARM (Harbison & Steele) book.
 * !!! NOTE: Additional facilities are provided in the form of allowing
 * the user to "bind" new conversion operation chars to arbitrary functions.
 */

/* Known bugs & limitations:
 *	sprintf depends on internal knowledge of STDIO, and thus is currently
 *	specific to KCC.  STDIO should provide a string-open function.
 *
 *	MAXSIGDIG must be set to at least the largest possible number of digits
 *	that can be output for any integer type, in order to avoid buffer
 *	overflow.  Making this dynamic is possible but would be extremely
 *	inefficient.  Floating point output will print "*" for any fields
 *	that would otherwise exceed MAXSIGDIG.  This constant (or something
 *	that it can be derived from) should be provided by c-env.h.
 *	Eventually the existence of the ANSI-draft <float.h> will provide
 *	useful constants that can be used here.
 *
 *	Floating-point output can be incorrect in the last digit (or two)
 *	if the user requests too great a precision, because the roundoff method
 *	fails when the hardware precision is exceeded.
 *	It would be possible to determine the max precision at runtime and
 *	then limit things to that, by modifying both prnd() and pff().
 *	Again, <float.h> will solve these problems.
 */
#ifdef COMMENT
Todo:
	Could improve efficiency of floating-point output stuff.
		e.g. prexp() is slow for large exponents.
	Conversion types could share more code at cost of some efficiency.

Routine provided to bind, unbind %-chars with routines.
(Also allow replacement of entire formatting scanner?)

int (*prf_bind)();		/* Returns previous binding value */

To bind:	prf_bind('i',prt_id);
To unbind:	prf_bind('i',0);
To replace scanner: prf_bind(0,prt_scan);
To restore scanner: prf_bind(0,0);

Conversion routine:
	prt_id(wp);			/* Pointer to workspace */

#endif /*COMMENT*/
#include <c-env.h>
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <printf.h>		/* To get workspace & routine defs */
#include <math.h>		/* For modf() and floor() */
#include <float.h>
#include <limits.h>
#include <string.h>

#if __STDC__
#define CONST const
#else
#define CONST
#endif

#define LONGISINT (LONG_MAX == INT_MAX)
#define SHORTISINT (SHRT_MAX == INT_MAX)
#define LDBLISDOUBLE (DBL_MANT_DIG == LDBL_MANT_DIG \
		&& DBL_MAX_EXP == LDBL_MAX_EXP)

#ifndef MAXSIGDIG
#define MAXSIGDIG 100
#endif


/* Imported routines */
extern char *calloc(), *realloc();
extern double modf(), floor();

/* Local routines */
static void prfill();
static void prfc(), prfs();
static void prfo(), prfx(), prfd(), prfu();
static void prff(), prfe(), prfg();
static void prfp(), prfn();

static void pflt();
static char *pd(),  *pu(), *po(),   *px(),  *pxu();
#if !LONGISINT
static char *pld(), *plu(), *plo(), *plx(), *plxu();
#endif
static char *pfi(), *pff(), *pexp();
static double prnd(), frexp10();
/* PRINTF - formatted output to stdout stream
*/
#if __STDC__
int printf(const char *format, ...)
#else
int
printf(format /*, ...*/)
char *format;
#endif
{
    va_list ap;
    int ret;

    va_start(ap, format);	/* Set up pointer to args */
    ret = vfprintf(stdout, format, ap);
    va_end(ap);
    return ret;
}

/* FPRINTF - formatted output to general FILE* stream
*/
#if __STDC__
int fprintf(FILE *stream, const char *format, ...)
#else
int
fprintf(stream, format /*, ...*/)
FILE *stream;
char *format;
#endif
{
    va_list ap;
    int ret;

    va_start(ap, format);	/* Set up pointer to args */
    ret = vfprintf(stream, format, ap);
    va_end(ap);
    return ret;
}

/* SPRINTF - formatted output to string
*/
#if __STDC__
int sprintf(char *s, const char *format, ...)
#else
int
sprintf(s, format /*, ...*/)
char *s;
char *format;
#endif
{
    va_list ap;
    int ret;

    va_start(ap, format);		/* Set up pointer to args */
    ret = vsprintf(s, format, ap);	/* output to string */
    va_end(ap);
    return ret;
}

/* VPRINTF - like PRINTF but with explicit arg list
*/
int
vprintf(format, ap)
CONST char *format;
va_list ap;
{
    return vfprintf(stdout, format, ap);
}

/* VSPRINTF - like SPRINTF but with explicit arg list
*/
int
vsprintf(s, format, ap)
char *s;
CONST char *format;
va_list ap;
{
    FILE *f;
    int ret;

    f = sopen(s, "w", 32767);		/* open string for output */
    ret = vfprintf(f, format, ap);	/* write to the string... */
    putc('\0', f);			/* null-terminate result */
    fclose(f);				/* "close" the string */
    return ret;
}
typedef void (*faddr)();	/* FADDR - function addr type */

/* For holding char bindings */
struct pf_bent {
	int pfe_type;	/* 0 - last entry */
	faddr pfe_rtn;
};

static struct pf_bent *_pfearray;
static int _pfecnt;

faddr
prf_bind(ch, rtn)
register int ch;
faddr rtn;
{	register struct pf_bent *ep;
	faddr ret;

	/* First see if char already exists */
	if(ep = _pfearray)
	  for(; ep->pfe_type; ++ep)
		if(ch == ep->pfe_type)
		  {	ret = ep->pfe_rtn;	/* Save old rtn val */
			if(rtn) ep->pfe_rtn = rtn;
			else ep->pfe_type = 0, ep->pfe_rtn = 0;
			return(ret);
		  }

	/* Not already in table, so add new entry. */
	if (ep)
	    ep = (struct pf_bent *) realloc((char *) _pfearray,
					++_pfecnt * sizeof(struct pf_bent));
	else
	    ep = (struct pf_bent *) calloc((_pfecnt=2),sizeof(struct pf_bent));
	_pfearray = ep;
	ep += _pfecnt-2;	/* Point to next-to-last entry */
	ep->pfe_type = ch;
	ep->pfe_rtn = rtn;
	(++ep)->pfe_type = 0;	/* Ensure last entry has zero type */
	return(0);
}
int
vfprintf(stream, format, vargp)
FILE *stream;
register CONST char *format;
va_list vargp;
{
    register int c;
    register struct pf_bent *ep;
    struct pf_work pfw;

    if (!_writeable(stream) || ferror(stream))
	return EOF;
    pfw.pfw_argp = vargp;	/* Set up pointer to args */
    pfw.pfw_fp = stream;
    pfw.pfw_cnt = 0;

    for (c = *format; c; c = *++format) {
	if (c != '%') {			/* If not start of a conversion, */
	    putc(c,stream);		/* just output quickly. */
	    pfw.pfw_cnt++;
	    continue;
	}

	/* Start handling % stuff! */

	/* First check for all optional flag chars */
	pfw.pfw_flag = 0;	/* Clear all flags */
	for (;;) {
	    switch (c = *++format) {
		case '-': pfw.pfw_flag |= PFF_NEG; continue;
		case '+': pfw.pfw_flag |= PFF_POS; continue;
		case '0': pfw.pfw_flag |= PFF_ZER; continue;
		case ' ': pfw.pfw_flag |= PFF_SPA; continue;
		case '#': pfw.pfw_flag |= PFF_ALT; continue;
		default: break;
	    }
	    break;		/* Break from switch is break from loop */
	}

	/* Set up fill char to use */
	pfw.pfw_fill = (pfw.pfw_flag & PFF_ZER) ? '0' : ' ';

	/* Get (optional) field width.
	** Note that if the '-' flag is given, left justification is always
	** done.  A negative '*' value merely (re)sets the PFF_NEG flag and
	** uses the abs value as the field width.  This is later negated
	** for left justification.
	*/
	if (c == '*') {
	    if ((pfw.pfw_fwid = va_arg(pfw.pfw_argp, int)) < 0) {
		pfw.pfw_flag |= PFF_NEG;	/* If neg, as if flag seen */
		pfw.pfw_fwid = -pfw.pfw_fwid;	/* with positive arg */
	    }
	    c = *++format;
	} else {
	    pfw.pfw_fwid = 0;		/* Initialize width */
	    while (isdigit(c)) {
		pfw.pfw_fwid = pfw.pfw_fwid * 10 + (c - '0');
		c = *++format;
	    }
	}
	if (pfw.pfw_flag&PFF_NEG)		/* If left justifying,  use */
	    pfw.pfw_fwid = -pfw.pfw_fwid;	/* negative field width */

	/* Get (optional) precision */
	if (c == '.') {
	    if((c = *++format) == '*') {
		pfw.pfw_prec = va_arg(pfw.pfw_argp, int);
		c = *++format;
	    } else {			/* Prec given, initialize and parse */
		pfw.pfw_prec = 0;
		while (isdigit(c)) {
		    pfw.pfw_prec = pfw.pfw_prec * 10 + (c - '0');
		    c = *++format;
		}
	    }
	} else pfw.pfw_prec = -1;		/* No precision given */

#if 0
	/* 4.2BSD used to have its '#' alternate-form flag, if present,
	** come after the precision.  This was fixed in 4.3BSD and so the
	** following check is no longer used.
	*/
	if (c == '#') {
	    pfw.pfw_flag |= PFF_ALT;
	    c = *++format;
	}
#endif

	/* Now check for (optional) modifiers */
	switch (c) {
	    case 'h': pfw.pfw_flag |= PFF_SHORT; c = *++format; break;
	    case 'l': pfw.pfw_flag |= PFF_LONG;  c = *++format; break;
	    case 'L': pfw.pfw_flag |= PFF_FLONG; c = *++format; break;
	}

	/* That was the last place in this file that we need to set the
	** modifier flags regardless of whether they are meaningful (so that
	** any dynamic bindings can see them).  Now zap any flags which
	** are redundant (same type as default) so that the compiler can
	** avoid compiling needless code from here on.
	*/
#if LONGISINT
#undef PFF_LONG
#define PFF_LONG 0
#endif
#if SHORTISINT
#undef PFF_SHORT
#define PFF_SHORT 0
#endif
#if LDBLISDOUBLE
#undef PFF_FLONG
#define PFF_FLONG 0
#endif

	/* Now handle the (required) conversion operation.
	** First scan dynamic %-char array to see if there
	** is any match, since those take precedence.
	*/
	pfw.pfw_char = c;	/* Store conversion operation char */
	if (ep = _pfearray) {
	    for (; ep->pfe_type; ++ep)
		if (ep->pfe_type == c)
		    break;
	    if (ep->pfe_type) {
		(*(ep->pfe_rtn))(&pfw);	/* Found it, invoke! */
		continue;		/* Then resume main loop! */
	    }
	}

	/* No match in the array, check standard built-in chars */
	switch (c) {
	case '%': prfill(&pfw, (char *)0, "%", 1, 0); break;

	case 'c': prfc(&pfw); break;	/* Char and string output */
	case 's': prfs(&pfw); break;

	case 'o': prfo(&pfw); break;	/* Integer output */
	case 'X':
	case 'x': prfx(&pfw); break;
	case 'u': prfu(&pfw); break;
	case 'i':
	case 'd': prfd(&pfw); break;

	case 'f': prff(&pfw); break;	/* Floating output */
	case 'E':
	case 'e': prfe(&pfw); break;
	case 'G':
	case 'g': prfg(&pfw); break;

	case 'p': prfp(&pfw); break;	/* ANSI: Pointer output */
	case 'n': prfn(&pfw); break;	/* ANSI: Set count */

	/* Unknown conversion char, print error msg */
	default:
	    fputs("(printf: '", pfw.pfw_fp);
	    putc(c, pfw.pfw_fp);
	    fputs("'?)", pfw.pfw_fp);
	    pfw.pfw_cnt += 14;
	    break;
	}
    }	/* End of %-handling, loop back to main scanner */

    /* Return EOF if any error, else # chars output */
    return ferror(pfw.pfw_fp) ? EOF : pfw.pfw_cnt;
}
/* PRFILL - General-purpose routine to handle field padding.
 */
static void
prfill(wp, pref, cval, clen, precf)
register struct pf_work *wp;
char *pref;	/* Prefix string (may be null ptr) */
char *cval;	/* Converted value string (must exist) */
int clen;	/* Length of value string */
int precf;	/* Additional precision fill (always >= 0) */
{	register int i;
	register int plen;	/* Length of prefix string if one */
	int fwid, totlen;

	plen = pref ? strlen(pref) : 0;
	wp->pfw_cnt += (totlen = precf + plen + clen);	/* Find # chars */

	if((fwid = wp->pfw_fwid) <= 0)
	  {	/* Left justified or no field */
		if(plen) fputs(pref, wp->pfw_fp);
		if((i = precf) > 0) {
			do putc('0', wp->pfw_fp);
			while(--i);
		}
		fputs(cval, wp->pfw_fp);
		if(fwid >= 0) return;	/* If no field width, done! */

		i = (-fwid) - totlen;
		if(i > 0) {
			wp->pfw_cnt += i;
			do putc(' ', wp->pfw_fp);
			while(--i);
		}
	  }
	else	/* Right justification */
	  {	
		if ((i = fwid - totlen) > 0)
			wp->pfw_cnt += i;	/* This much left padding */

		/* Do either 0-padding or SP-padding */
		if(i > 0 && (wp->pfw_flag&PFF_ZER)==0) {
			do putc(' ', wp->pfw_fp);
			while(--i);
		}
		if(plen) fputs(pref, wp->pfw_fp);
		if(i > 0 && (wp->pfw_flag&PFF_ZER)!=0) {
			do putc('0', wp->pfw_fp);
			while(--i);
		}

		if((i = precf) > 0) {
			do putc('0', wp->pfw_fp);
			while(--i);
		}
		fputs(cval, wp->pfw_fp);
	  }
}
/* PRFN - 'n' Return output count
*/
static void
prfn(wp)
register struct pf_work *wp;
{
    if (!(wp->pfw_flag&(PFF_SHORT|PFF_LONG)))
	*(va_arg(wp->pfw_argp, int *)) = wp->pfw_cnt;
    else if (wp->pfw_flag & PFF_LONG)
	*(va_arg(wp->pfw_argp, long *)) = wp->pfw_cnt;
    else *(va_arg(wp->pfw_argp, short *)) = wp->pfw_cnt;
}
/* Character and String conversions */

/* PRFC - 'c' Character output
**	Only uses field width and the flags '-' and '0'.
** NOTE: ANSI does not define the behavior for the '0' flags, but H&S
** claims this causes '0' to be used as the fill character.  Rather than
** be picky, we permit this.
*/
static void
prfc(wp)
register struct pf_work *wp;
{	char tmpbuf[2];

	tmpbuf[0] = va_arg(wp->pfw_argp, int);
	tmpbuf[1] = '\0';
	prfill(wp, (char *)0, tmpbuf, 1, 0);
}

/* PRFS - 's' String output
**	Uses field width, precision, and flags '-', '0'.
**	Positive precision truncates length to that value.
** NOTE: situation with the '0' flag is same as for 'c'.
*/
static void
prfs(wp)
struct pf_work *wp;
{
	register char *cp;
	register int len, i;
	int trunc;

	cp = va_arg(wp->pfw_argp, char *);	/* Get pointer to string */
	if(cp == (char *)0)
		cp = "(null)";			/* Be kind to zero ptrs */
	len = strlen(cp);			/* Find length of string */

	/* Special fast check for usual case of %s */
	if(wp->pfw_fwid == 0 && wp->pfw_prec <= 0)
	  {	fputs(cp, wp->pfw_fp);		/* Left adj, no prec */
		wp->pfw_cnt += len;
		return;			
	  }

	/* Check length against precision to see if truncation needed */
	trunc = 0;
	if(wp->pfw_prec > 0 && wp->pfw_prec < len)
	  {	len = wp->pfw_prec;
		trunc++;	/* Sigh, remember to truncate string */
	  }

	/* See whether to prepad */
	if((i = wp->pfw_fwid - len) > 0)
	  {	wp->pfw_cnt += i;
		do putc(wp->pfw_fill, wp->pfw_fp);
		while(--i);
	  }

	/* Now output string */
	wp->pfw_cnt += len;
	if(!trunc) fputs(cp, wp->pfw_fp);
	else if((i = len) > 0)
	  {	do putc(*cp++, wp->pfw_fp);
		while(--i);
	  }

	/* Now postpad if necessary */
	if(wp->pfw_fwid < 0 && (i = -(wp->pfw_fwid) - len) > 0)
	  {	wp->pfw_cnt += i;
		do putc(' ', wp->pfw_fp);
		while(--i);
	  }
}
/* Pointer conversion */

/* PRFP - 'p' pointer representation output
**
*/
static void
prfp(wp)
register struct pf_work *wp;
{
    if (sizeof(int) == sizeof(void *)) {
#if !LONGISINT
	wp->pfw_flag &= ~PFF_LONG;
#endif
	prfo(wp);
    }
#if !LONGISINT
    else if (sizeof(long) == sizeof(void *)) {
	wp->pfw_flag |= PFF_LONG;
	prfo(wp);
    }
#endif
    else prfill(wp, (char *)NULL, "(can't)", 7, 0);
}
/* Integer conversions */

/* PRFD - 'd' and 'i' Signed Decimal number output
**	
*/
static void
prfd(wp)
register struct pf_work *wp;
{
	char *cp;
	char tmpbuf[MAXSIGDIG];
	int arg, i, len;
	char *pref;

	pref = NULL;
#if !LONGISINT
	if(wp->pfw_flag&PFF_LONG)
	  {	if((larg = va_arg(wp->pfw_argp, long)) < 0)
		  {	pref = "-";
			larg = -larg;
		  }
		if (larg == -larg) cp = plu(tmpbuf, larg); /* Handle max neg */
		else cp = pld(tmpbuf, larg);
	  }
	else
#endif
	  {
#if !SHORTISINT
		if (wp->pfw_flag & PFF_SHORT)
		    arg = va_arg(wp->pfw_argp, short);
		else
#endif
		    arg = va_arg(wp->pfw_argp, int);
		if (arg < 0)
		  {	pref = "-";
			arg = -arg;
		  }
		if (arg == -arg) cp = pu(tmpbuf, arg);	/* Handle max neg */
		else cp = pd(tmpbuf, arg);
	  }
	len = cp - tmpbuf;
	*++cp = '\0';		/* Tie off result */

	if(!pref)
	  {	if     (wp->pfw_flag&PFF_POS) pref = "+";
		else if(wp->pfw_flag&PFF_SPA) pref = " ";
	  }

	if ((i = wp->pfw_prec) >= 0) {	/* Precision given? */
	    wp->pfw_flag &= ~PFF_ZER;	/* Ignore '0' flag */
	    if (i == 0 && tmpbuf[1] == '0')	/* Special case */
		tmpbuf[1] = '\0';		/* can produce null string */
	    else i = wp->pfw_prec - len;
	}
	prfill(wp, pref, tmpbuf+1, len, i < 0 ? 0 : i);
}


/* PRFO - 'o' Octal number output
**	
**	# flag ensures that number starts with '0'.
**	+ and SP flags ignored.
*/
static void
prfo(wp)
register struct pf_work *wp;
{
	register int len, prec;
	register char *cp;
	char tmpbuf[MAXSIGDIG];		/* For bare-bones octal string */

	if (!(wp->pfw_flag&(PFF_SHORT|PFF_LONG)))
		cp = po(tmpbuf, va_arg(wp->pfw_argp, unsigned int));
#if !LONGISINT
	else if (wp->pfw_flag & PFF_LONG)
		cp = plo(tmpbuf, va_arg(wp->pfw_argp, unsigned long));
#endif
	else	cp = po(tmpbuf, (unsigned short)va_arg(wp->pfw_argp, unsigned int));

	len = cp - tmpbuf;
	*++cp = '\0';		/* Tie off result */

    /* If no precision was specified, 1 is used as default, but there's no
    ** need to set this explicitly since the code gives prfill the right stuff.
    */
	if ((prec = wp->pfw_prec) >= 0) {	/* User specified precision? */
	    wp->pfw_flag &= ~PFF_ZER;		/* Yes, ignore '0' flag */
	    if (prec == 0			/* Check for case of 0 prec */
	       && tmpbuf[1] == '0') {		/* and 0 arg, which */
		tmpbuf[1] = '\0';		/* produces null string! */
		len = 0;
	    } else if ((prec -= len) < 0)	/* Turn positive prec into */
		prec = 0;			/* # of leading 0s to use */
	} else prec = 0;

    /* Check for '#' flag hackery */
	if ((wp->pfw_flag & PFF_ALT)	/* Special hack for %o */
	  && prec < 1			/* If precision maybe not big enuf */
	  && tmpbuf[1] != '0')		/* and doesn't already start with 0 */
		++prec;			/* bump to force a leading 0! */

	prfill(wp,		/* Workspace ptr */
		(char *)0,	/* Prefix */
		tmpbuf+1,	/* Converted val */
		len,		/* Length of converted str */
		prec);		/* # of additional precision digits */
}

/* PRFX - 'x' and 'X' Hexadecimal number output
**	
*/
static void
prfx(wp)
register struct pf_work *wp;
{
	register int i, len;
	char *cp;
	char tmpbuf[MAXSIGDIG];

	if (!(wp->pfw_flag&(PFF_SHORT|PFF_LONG)))
	  {	if(wp->pfw_char == 'X')
			cp = pxu(tmpbuf, va_arg(wp->pfw_argp, unsigned int));
		else    cp = px (tmpbuf, va_arg(wp->pfw_argp, unsigned int));
	  }
#if !LONGISINT
	else if (wp->pfw_flag&PFF_LONG)
	  {	if(wp->pfw_char == 'X')
			cp = plxu(tmpbuf, va_arg(wp->pfw_argp, unsigned long));
		else    cp = plx (tmpbuf, va_arg(wp->pfw_argp, unsigned long));
	  }
#endif
	else
	  {	if(wp->pfw_char == 'X')
			cp = pxu(tmpbuf, (unsigned short)va_arg(wp->pfw_argp, unsigned int));
		else    cp = px (tmpbuf, (unsigned short)va_arg(wp->pfw_argp, unsigned int));
	  }
	len = cp - tmpbuf;
	*++cp = '\0';		/* Tie off result */

	if ((i = wp->pfw_prec) >= 0) {	/* Precision given? */
	    wp->pfw_flag &= ~PFF_ZER;	/* Ignore '0' flag */
	    if (i == 0 && tmpbuf[1] == '0')	/* Special case */
		tmpbuf[1] = '\0';		/* can produce null string */
	    else i = wp->pfw_prec - len;
	}

	prfill(wp,
		(((wp->pfw_flag&PFF_ALT) && tmpbuf[1] != '0') ?
			((wp->pfw_char == 'X') ? "0X" : "0x")
			: (char *) 0),
		tmpbuf+1,
		len,
		i < 0 ? 0 : i);
}

/* PRFU - 'u' Unsigned decimal number output
 *	
 */
static void
prfu(wp)
register struct pf_work *wp;
{
	register int i, len;
	char *cp;
	char tmpbuf[MAXSIGDIG];

#if !LONGISINT
	if(wp->pfw_flag&PFF_LONG)
		cp = plu(tmpbuf, va_arg(wp->pfw_argp, unsigned long));
	else
#endif
	     if (wp->pfw_flag&PFF_SHORT)
		cp = pu (tmpbuf, (short)va_arg(wp->pfw_argp, unsigned int));
	else    cp = pu (tmpbuf, va_arg(wp->pfw_argp, unsigned int));
	len = cp - tmpbuf;
	*++cp = '\0';		/* Tie off result */

	if ((i = wp->pfw_prec) >= 0) {	/* Precision given? */
	    wp->pfw_flag &= ~PFF_ZER;	/* Ignore '0' flag */
	    if (i == 0 && tmpbuf[1] == '0')	/* Special case */
		tmpbuf[1] = '\0';		/* can produce null string */
	    else i = wp->pfw_prec - len;
	}
	prfill(wp, (char *)0, tmpbuf+1, len, i < 0 ? 0 : i);
}
/* Actual number conversion subroutines.
**	All of these routines take two arguments:
**	"s" - pointer to last location deposited into.  New string will
**		be added just after that, WITHOUT a trailing null char.
**	"n" - value to convert.
** The return value will be a pointer to the last character deposited.
** The caller must null-terminate the string if desired.
*/

static
#if CPU_PDP10
	int		/* Use more efficient addressing on PDP10s */
#else
	char
#endif
    pntab[16] = {
	'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' },
    pxtab[16] = {
	'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };


static char *
pd(s, n)		/* Decimal */
char *s;
int n;
{
    int d = n % 10;		/* Get low digit */
    if (n/10) s = pd(s, n/10);	/* Do high order digits */
    *++s = pntab[d];		/* Output low digit */
    return s;
}


#if !LONGISINT
static char *
pld(s, n)		/* Long Decimal */
char *s;
long n;
{
    int d = n % 10;		/* Get low digit */
    if (n/10) s = pld(s, n/10);	/* Do high order digits */
    *++s = pntab[d];		/* Output low digit */
    return s;
}
#endif

static char *
pu(s, n)		/* Unsigned Decimal */
char *s;
unsigned int n;
{
    int d = n % 10;		/* Get low digit */
    if (n/10) s = pd(s, (int)(n/10));	/* Now can do high digits signed for speed! */
    *++s = pntab[d];		/* Output low digit */
    return s;
}

#if !LONGISINT
static char *
plu(s, n)		/* Long Unsigned Decimal */
char *s;
unsigned long n;
{
    int d = n % 10;		/* Get low digit */
    if (n/10) s = pld(s, n/10);	/* Now can do high digits signed for speed! */
    *++s = pntab[d];		/* Output low digit */
    return s;
}
#endif

static char *
po(s, n)		/* Octal */
char *s;
unsigned int n;
{
    if (n &~ 07) s = po(s, n >> 3);	/* Do high order digits */
    *++s = pntab[n & 07];	/* Output low digit */
    return s;
}

#if !LONGISINT
static char *
plo(s, n)		/* Long Octal */
char *s;
unsigned long n;
{
    if (n &~ 07) s = plo(s, n >> 3);	/* do high order digits */
    *++s = pntab[n & 07];		/* bottom digit */
    return s;
}
#endif

static char *
px(s, n)		/* Hexadecimal */
char *s;
unsigned int n;
{
    if (n & ~(0xF)) s = px(s, n >> 4);	/* do high order digits */
    *++s = pntab[n & 0xF];		/* Do bottom digit */
    return s;
}

#if !LONGISINT
static char *
plx(s, n)		/* Long Hexadecimal */
char *s;
unsigned long n;
{
    if (n & ~(0xF)) s = plx(s, n >> 4);	/* do high order digits */
    *++s = pntab[n & 0xF];	/* Do bottom digit */
    return s;
}
#endif

static char *
pxu(s, n)		/* Hexadecimal Uppercase */
char *s;
unsigned int n;
{
    if (n & ~(0xF)) s = pxu(s, n >> 4);	/* do high order digits */
    *++s = pxtab[n & 0xF];		/* Do bottom digit */
    return s;
}

#if !LONGISINT
static char *
plxu(s, n)		/* Long Hexadecimal Uppercase */
char *s;
unsigned long n;
{
    if (n & ~(0xF)) s = plxu(s, n >> 4);	/* do high order digits */
    *++s = pxtab[n & 0xF];		/* Do bottom digit */
    return s;
}
#endif
/* Floating-point number conversions */

/* PRFF - 'f' format output
 */
static void
prff(wp)
struct pf_work *wp;
{	pflt(wp, 0);		/* Invoke general routine with right flag */
}

/* PRFE - 'e' format output
 */
static void
prfe(wp)
struct pf_work *wp;
{	pflt(wp, 1);		/* Invoke general routine with right flag */
}

/* PRFG - 'g' format output
 */
static void
prfg(wp)
struct pf_work *wp;
{	pflt(wp, -1);		/* Invoke general routine with right flag */
}
/* PFLT - auxiliary to do 'f', 'e', or 'g' format output
 *	eflg:	-1 for G format
 *		 0 for F format
 *		+1 for E format
 */
static void
pflt(wp, eflg)
struct pf_work *wp;
int eflg;
{	/* Allow MAXSIGDIG for each of integer, fraction, and exponent */
	/* plus decimal point (.) and exponent (E+) and terminator */
	char tmpbuf[MAXSIGDIG*3+4];
	register char *cp = tmpbuf;
	char *pref;
	int exp, gflg;
	double arg, frarg;
	static double dzero = 0.0;

	if (wp->pfw_prec < 0)
		wp->pfw_prec = 6; /* digits after point */

	arg = va_arg(wp->pfw_argp, double);
	if(arg < 0)
	  {	pref = "-";
		arg = -arg;
	  }
	else
	  {	if     (wp->pfw_flag&PFF_POS) pref = "+";
		else if(wp->pfw_flag&PFF_SPA) pref = " ";
		else pref = 0;
	  }
#if CPU_PDP10		/* Check for properly normalized argument */
	if (arg && !((*(int *)&arg) & (1<<(FLT_MANT_DIG-1)))) {
	    arg += dzero;	/* Normalize the arg */
	    *++cp = '#';	/* and report by starting string with flag */
	}
#endif

	if(eflg > 0)		/* If E fmt */
	  {	gflg = 0;	/* Not G fmt */

		/* Split number into fraction and exponent */
		if((frarg = frexp10(arg, &exp)) != 0.0)
		  {	/* Adj to have 1 digit ahead of pt, and round off. */
			exp--;
		  }
	  }
	else if(eflg < 0)	/* If G fmt */
	  {	gflg = 1;
		if(wp->pfw_prec > 0)		/* G format bumps prec */
			--(wp->pfw_prec);	/*  down for internal use */

		/* Split number into fraction and exponent */
		frarg = frexp10(arg, &exp);

		/* Now check exponent value to determine which format to use.
		 * Note value of 0.0 will always win even though exponent
		 * is incorrectly munged to -1, because F format will be
		 * selected instead of E.
		 */
		--exp;		/* Get actual exponent we'd use */
		eflg = (exp < -4 || wp->pfw_prec < exp); /* TRUE if E fmt */

		if(!eflg)
			wp->pfw_prec -= exp;	/* Adjust precision for F */
	  }
	else			/* F format */
	  {	gflg = 0;
	  }

	/* Round off to desired precision */
	arg = prnd((eflg ? frarg*10.0	/* E uses bumped fraction */
			 : arg),	/* F uses original arg */
			wp->pfw_prec);

	/* Now deposit string to represent the number.
	 * Both pfi and pff limit their output to MAXSIGDIG digits.
	 */
	cp = pfi(arg, cp);			/* Deposit integer */
	if(wp->pfw_prec != 0 || (wp->pfw_flag&PFF_ALT) || gflg)
		cp = pff(arg, cp, wp->pfw_prec);	/* Deposit fraction */

	/* Now, if PFF_ALT is NOT set for G fmt, must strip trailing zeros */
	if(gflg && (wp->pfw_flag&PFF_ALT)==0)
	  {	while(*cp == '0') --cp;
		if(*cp == '.') --cp;	/* Flush point too if run into it */
	  }

	if(eflg)
	  {	/* Now finish off with exponent */
		if(isupper(wp->pfw_char)) *++cp = 'E';
		else *++cp = 'e';
		cp = pexp(exp, cp);
	  }
	*++cp = '\0';			/* Finalize string */

	prfill(wp, pref, tmpbuf+1, cp - (tmpbuf+1), 0);
}
/* Floating-point auxiliary routines */

/* PRND - Round number to desired precision.
 *	prec - # of digits to right of decimal point.
 *	Method is to add 0.5 * 10^(-prec)
 *	e.g. if prec is 2, want round to .nn so we add .005 to number.
 */
static double
prnd(d, prec)
double d;
int prec;
{	double rndoff;

#ifdef COMMENT
/* This is not completely thought out; needs coordination with pff */
	static int maxprec = 0;
	static double minrnd;
	while(prec > maxprec)
	  {
		if(maxprec)		/* See if already initialized */
			return(d+minrnd);	/* Yes, fast return */

		/* No, find what our maximum precision is */
		rndoff = 0.5;
		do {	minrnd = rndoff;
			rndoff /= 10.0;
			++maxprec;
		  } while((1.0 + rndoff) > 1.0);
		--maxprec;
	  }
#endif /*COMMENT*/
    rndoff = 0.5;
    while (--prec >= 0)
	if ((rndoff /= 10.0) > 1.0)	/* If roundoff value underflows, */
	    return d;			/* then no rounding. */
    return (d+rndoff);
}

/* PFI - print integer part of a double.
 *	Cannot use a single modf call because the integer part may
 *	be larger than an int.  We also want to avoid depending on
 *	any guess about how many digits an int can hold, so we end
 *	up calling modf on each digit.
 *	If # digits is greater than limit, "*" will be output.
 */
static char *
pfi(d, s)
double d;
char *s;
{	double i;
	int exp;

	d = floor(d) + 0.5;	/* Ensure we'll round the integer correctly */
	d = frexp10(d, &exp);	/* Find # digits to left (exponent) */
	if(exp > MAXSIGDIG)
		*++s = '*';
	else if(exp > 0)
		while(--exp >= 0)
		  {	d = modf(d * 10.0, &i);
			*++s = pntab[(int)i];
		  }
	else *++s = '0';
	return s;
}

/* PFF - Print fractional part of a double.
 *	If precision is zero or negative, only a decimal point is printed.
 */
static char *
pff(d, cp, prec)
double d;
register char *cp;
int prec;
{	double junk;

	*++cp = '.';
	d = modf(d, &junk);		/* Get fractional part */

	if(prec > MAXSIGDIG)
		*++cp = '*';
	else while(prec-- > 0)
	  {	d = modf(d*10.0, &junk);
		*++cp = pntab[(int)junk];
	  }
	return cp;
}

/* PEXP - print exponent
 */
static char *
pexp(exp, cp)
int exp;
char *cp;
{	
	if(exp < 0)
	  {	exp = -exp;
		*++cp = '-';
	  }
	else *++cp = '+';
	if(exp >= 100) cp = pd(cp, exp);
	else	/* Must always print at least 2 digits of exponent */
	  {	*++cp = pntab[exp/10];
		*++cp = pntab[exp%10];
	  }
	return cp;
}

/* FREXP10 - like "frexp" but exponent is base 10 and returns
 *	1.0 > result >= .1
 *	Arg must be positive.
 */
static double
frexp10(d, ip)
double d;
int *ip;
{	register int i;

	i = 0;
	if (d >= 1.0)
	    for(; d >= 1.0; ++i)	/* Divide until within range */
		d /= 10.0;
	else for(; d && d < 0.1; --i)	/* Multiply until within range */
		d *= 10.0;
	*ip = i;
	return(d);
}