Browse Source

Fix a couple of bugs in printf. Add floating pt output for doubles. Yeah!
Modified stdio/Makefile to build printf.c according to Config values for
HAS_FLOATS and HAS_LONG_LONG.

Manuel Novoa III 23 years ago
parent
commit
35ec5000a5
3 changed files with 306 additions and 113 deletions
  1. 262 106
      libc/misc/internals/dtostr.c
  2. 11 2
      libc/stdio/Makefile
  3. 33 5
      libc/stdio/printf.c

+ 262 - 106
libc/misc/internals/dtostr.c

@@ -1,54 +1,26 @@
 /*
- * Copyright (C) 2000 Manuel Novoa III
+ * Copyright (C) 2000, 2001 Manuel Novoa III
  *
- * Function:  const char *__dtostr(double x)
+ * Function:  int __dtostr(FILE * fp, size_t size, double x, 
+ *			               char flag[], int width, int preci, char mode)
  *
- * This was written for uClibc to give it the ability to at least output
- * floating point values in exponential form.  No formatting is done.
- * No trailing 0's are removed.  Number of digits generated is not
- * runtime selectable.  It does however handle +/- infinity and nan on i386.
- *
- * The goal was usable floating point %e-type output in minimal size.  For
- * me, "gcc -c -Os -fomit-frame-pointer dtostr.c && size dtostr.o" gives
- *   text    data     bss    dec     hex filename
- *   535       9      26     570     23a dtostr.o    (WANT_EXP_FORM = 1)
- *   530       9      26     565     235 dtostr.o    (WANT_EXP_FORM = 0)
- *
- * Output is of the form [-][#].######{e|E}{+|-}##.  Choices of upper or
- * lower case exponent character, and initial digit/initial decimal forms
- * are compile-time options.  Number of digits generated is also selected
- * at compile time (MAX_DIGITS).
+ * This was written for uClibc to provide floating point support for
+ * the printf functions.  It handles +/- infinity and nan on i386.
  *
  * Notes:
  *
- * The primary objective of this implementation was minimal size while
- * maintaining reasonable accuracy.  It should also be fairly portable,
- * as not assumptions are made about the bit-layout of doubles.
+ * It should also be fairly portable, as not assumptions are made about the
+ * bit-layout of doubles.
  *
  * It should be too difficult to convert this to handle long doubles on i386.
  * For information, see the comments below.
  *
- * There are 2 compile-time options below, as well as some tuning parameters.
- *
  * TODO: 
  *   long double and/or float version?  (note: for float can trim code some).
+ *   
+ *   Decrease the size.  This is really much bigger than I'd like.
  */
 
-/*****************************************************************************/
-/*                            OPTIONS                                        */
-/*****************************************************************************/
-
-/*
- * Set this if you want output results with 1 digit before the decimal point.
- * If this is 0, all digits follow a leading decimal point.
- */
-#define WANT_EXP_FORM   1
-
-/*
- * Set if you want exponent character 'E' rather than 'e'.
- */
-#define EXP_UPPERCASE   1
-
 /*****************************************************************************/
 /* Don't change anything that follows unless you know what you're doing.     */
 /*****************************************************************************/
@@ -67,6 +39,11 @@
  */
 #define MAX_DIGITS          17
 
+/*
+ * Set this to the smallest integer type capable of storing a pointer.
+ */
+#define INT_OR_PTR int
+
 /*
  * This is really only used to check for infinities.  The macro produces
  * smaller code for i386 and, since this is tested before any floating point
@@ -90,9 +67,25 @@ extern int _zero_or_inf_check(double x);
 /* Don't change anything that follows peroid!!!  ;-)                         */
 /*****************************************************************************/
 
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
 #include <float.h>
 #include <limits.h>
 
+extern int fnprintf(FILE * fp, size_t size, const char *fmt, ...);
+
+/* from printf.c -- should really be in an internal header file */
+enum {
+	FLAG_PLUS = 0,
+	FLAG_MINUS_LJUSTIFY,
+	FLAG_HASH,
+	FLAG_0_PAD,
+	FLAG_SPACE,
+};
+
+/*****************************************************************************/
+
 /*
  * Set things up for the scaling power table.
  */
@@ -119,60 +112,88 @@ extern int _zero_or_inf_check(double x);
 
 #if (INT_MAX >> 30)
 #define DIGIT_BLOCK_TYPE     int
+#define DB_FMT               "%.*d"
 #elif (LONG_MAX >> 30)
 #define DIGIT_BLOCK_TYPE     long
+#define DB_FMT               "%.*ld"
 #else
 #error need at least 32 bit longs
 #endif
 
-/*
- * This is kind of a place-holder for LONG_DOUBLE support to show what I
- * think needs to be changed.  I haven't tried it though.  Changing this
- * from 3 to 4 and converting double to long double should work on i386.
- * DON'T FORGET to increase EXP_TABLE_SIZE and MAX_DIGITS.
- * DON'T FORGET the "larger EXP_TABLE_SIZE needed" check above.
- */
-#define MAX_EXP_DIGITS 3
+/* Are there actually any machines where this might fail? */
+#if 'A' > 'a'
+#error ordering assumption violated : 'A' > 'a'
+#endif
+
+/* Maximum number of calls to fnprintf to output double. */
+#define MAX_CALLS 8
 
 /*****************************************************************************/
 
 #define NUM_DIGIT_BLOCKS   ((MAX_DIGITS+DIGITS_PER_BLOCK-1)/DIGITS_PER_BLOCK)
 
-static char infstr[] = " inf";	/* save space for a - sign */
-static char nanstr[] = "nan";
-
 /* extra space for '-', '.', 'e+###', and nul */
-/*static char buf[ 5 + MAX_EXP_DIGITS + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK];*/
-#define BUF_SIZE  ( 5 + MAX_EXP_DIGITS + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK )
+#define BUF_SIZE  ( 3 + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK )
 /*****************************************************************************/
 
-const char *__dtostr(char *buf, double x)
+static const char *fmts[] = {
+	"%0*d", "%.*s", ".", "inf", "INF", "nan", "NAN", "%*s"
+};
+
+/*****************************************************************************/
+
+int __dtostr(FILE * fp, size_t size, double x, 
+			 char flag[], int width, int preci, char mode)
 {
 	double exp_table[EXP_TABLE_SIZE];
 	double p10;
 	DIGIT_BLOCK_TYPE digit_block; /* int of at least 32 bits */
 	int i, j;
+	int round, o_exp;
 	int exp, exp_neg;
-	int negative = 0;
-	char *pos;
-
+	char *s;
+	char *e;
+	char buf[BUF_SIZE];
+	char buf2[BUF_SIZE];
+	INT_OR_PTR pc_fwi[2*MAX_CALLS];
+	INT_OR_PTR *ppc;
+	char exp_buf[8];
+	char drvr[8];
+	char *pdrvr;
+	int npc;
+	int cnt;
+	char sign_str[2];
+	char o_mode;
+
+	/* check that INT_OR_PTR is sufficiently large */
+	assert( sizeof(INT_OR_PTR) == sizeof(char *) );
+
+	*sign_str = flag[FLAG_PLUS];
+	*(sign_str+1) = 0;
 	if (isnan(x)) {				/* nan check */
-		return nanstr;
+		pdrvr = drvr + 1;
+		*pdrvr++ = 5 + (mode < 'a');
+		pc_fwi[2] = 3;
+		flag[FLAG_0_PAD] = 0;
+		goto EXIT_SPECIAL;
 	}
 
 	if (x == 0) {				/* handle 0 now to avoid false positive */
-		exp = 0;				/* with inf test, and to avoid scaling  */
-		goto GENERATE_DIGITS;	/* note: time vs space tradeoff */
+		exp = -1;
+		goto GENERATE_DIGITS;
 	}
 
 	if (x < 0) {				/* convert negatives to positives */
-		negative = 1;
+		*sign_str = '-';
 		x = -x;
 	}
 
 	if (_zero_or_inf_check(x)) { /* must be inf since zero handled above */
-		pos = infstr + 1;
-		goto DO_SIGN;
+		pdrvr = drvr + 1;
+		*pdrvr++ = 3 +  + (mode < 'a');
+		pc_fwi[2] = 3;
+		flag[FLAG_0_PAD] = 0;
+		goto EXIT_SPECIAL;
 	}
 
 	/* need to build the scaling table */
@@ -186,11 +207,7 @@ const char *__dtostr(char *buf, double x)
 		exp_neg = 1;
 	}
 
-#if WANT_EXP_FORM
 	exp = DIGITS_PER_BLOCK - 1;
-#else
-	exp = DIGITS_PER_BLOCK;
-#endif
 
 	i = EXP_TABLE_SIZE;
 	j = EXP_TABLE_MAX;
@@ -208,64 +225,203 @@ const char *__dtostr(char *buf, double x)
 		}
 		j >>= 1;
 	}
+	if (x >= 1e9) {				/* handle bad rounding case */
+		x /= 10;
+		++exp;
+	}
+	assert(x < 1e9);
 
  GENERATE_DIGITS:
-	pos = buf - BUF_SIZE + 1 + DIGITS_PER_BLOCK + 1; /* leave space for '.' and - */
+	s = buf2 + 2; /* leave space for '\0' and '0' */
 
 	for (i = 0 ; i < NUM_DIGIT_BLOCKS ; ++i ) {
-		digit_block = (int) x;
+		digit_block = (DIGIT_BLOCK_TYPE) x;
 		x = (x - digit_block) * 1e9;
-		for (j = 0 ; j < DIGITS_PER_BLOCK ; j++) {
-			*--pos = '0' + (digit_block % 10);
-			digit_block /= 10;
+		s += sprintf(s, DB_FMT, DIGITS_PER_BLOCK, digit_block);
+	}
+
+	/*************************************************************************/
+
+	*exp_buf = 'e';
+	if (mode < 'a') {
+		*exp_buf = 'E';
+		mode += ('a' - 'A');
+	} 
+
+	o_mode = mode;
+
+	round = preci;
+
+	if ((mode == 'g') && (round > 0)){
+		--round;
+	}
+
+	if (mode == 'f') {
+		round += exp;
+	}
+
+ RESTART:
+	memcpy(buf,buf2,sizeof(buf2)); /* backup in case g need to be f */
+
+	s = buf;
+	*s++ = 0;					/* terminator for rounding and 0-triming */
+	*s = '0';					/* space to round */
+
+	i = 0;
+	e = s + MAX_DIGITS + 1;
+	if (round < MAX_DIGITS) {
+		e = s + round + 2;
+		if (*e >= '5') {
+			i = 1;
 		}
-		pos += (2*DIGITS_PER_BLOCK);
 	}
-	pos -= (DIGITS_PER_BLOCK*(NUM_DIGIT_BLOCKS+1))-MAX_DIGITS;
 
-	/* start generating the exponent */
-#if EXP_UPPERCASE
-	*pos = 'E';
-#else
-	*pos = 'e';
-#endif
-	*++pos = '+';
-	if (exp < 0) {
-		*pos = '-';
-		exp = -exp;
+	do {						/* handle rounding and trim trailing 0s */
+		*--e += i;				/* add the carry */
+	} while ((*e == '0') || (*e > '9'));
+
+	o_exp = exp;
+	if (e <= s) {				/* we carried into extra digit */
+		++o_exp;
+		e = s;					/* needed if all 0s */
+	} else {
+		++s;
+	}
+	*++e = 0;					/* ending nul char */
+
+	if ((mode == 'g') && ((o_exp >= -4) && (o_exp < round))) {
+		mode = 'f';
+		goto RESTART;
 	}
-	pos += 3;					/* WARNING: Assumes max exp < 1000!!! */
-	if (exp >= 100) {
-		++pos;
-#if MAX_EXP_DIGITS > 4
-#error need to modify exponent string generation code
-#elif MAX_EXP_DIGITS > 3
-		if (exp >= 1000) {		/* WARNING: hasn't been checked */
-			++pos;				/*    but should work */
+
+	exp = o_exp;
+	if (mode != 'f') {
+		o_exp = 0;
+	}
+
+	if (o_exp < 0) {
+		*--s = '0';				/* fake the first digit */
+	}
+
+	pdrvr = drvr+1;
+	ppc = pc_fwi+2;
+
+	*pdrvr++ = 0;
+	*ppc++ = 1;
+	*ppc++ = (INT_OR_PTR)(*s++ - '0');
+
+	i = e - s;					/* total digits */
+	if (o_exp >= 0) {
+		if (o_exp >= i) {		/* all digit(s) left of decimal */
+			*pdrvr++ = 1;
+			*ppc++ = i;
+			*ppc++ = (INT_OR_PTR)(s);
+			o_exp -= i;
+			i = 0;
+			if (o_exp>0) {		/* have 0s left of decimal */
+				*pdrvr++ = 0;
+				*ppc++ = o_exp;
+				*ppc++ = 0;
+			}
+		} else if (o_exp > 0) {	/* decimal between digits */
+			*pdrvr++ = 1;
+			*ppc++ = o_exp;
+			*ppc++ = (INT_OR_PTR)(s);
+			s += o_exp;
+			i -= o_exp;
 		}
-#endif
+		o_exp = -1;
+	}
+
+	if (flag[FLAG_HASH] || (i) || ((o_mode != 'g') && (preci > 0))) {
+		*pdrvr++ = 2;			/* need decimal */
+		*ppc++ = 1;				/* needed for width calc */
+		ppc++;
 	}
-	*pos = '\0';
-		
-	for (j = 0 ; (j < 2) || exp ; j++) { /* standard says at least 2 digits */
-		*--pos = '0' + (exp % 10);
-		exp /= 10;
+
+	if (++o_exp < 0) {			/* have 0s right of decimal */
+		*pdrvr++ = 0;
+		*ppc++ = -o_exp;
+		*ppc++ = 0;
+	}
+	if (i) {					/* have digit(s) right of decimal */
+		*pdrvr++ = 1;
+		*ppc++ = i;
+		*ppc++ = (INT_OR_PTR)(s);
+	}
+
+	if (o_mode != 'g') {
+		i -= o_exp;
+		if (i < preci) {		/* have 0s right of digits */
+			i = preci - i;
+			*pdrvr++ = 0;
+			*ppc++ = i;
+			*ppc++ = 0;
+		}
 	}
 
-	/* insert the decimal point */
-	pos = buf - BUF_SIZE + 1;
+	/* build exponent string */
+	if (mode != 'f') {
+		*pdrvr++ = 1;
+		*ppc++ = sprintf(exp_buf,"%c%+.2d", *exp_buf, exp);
+		*ppc++ = (INT_OR_PTR) exp_buf;
+	}
 
-#if WANT_EXP_FORM
-	*pos = *(pos+1);
-	*(pos+1) = '.';
+ EXIT_SPECIAL:
+	npc = pdrvr - drvr;
+	ppc = pc_fwi + 2;
+	for (i=1 ; i< npc ; i++) {
+		width -= *(ppc++);
+		ppc++;
+	}
+	i = 0;
+	if (*sign_str) {
+		i = 1;
+	}
+	width -= i;
+	if (width <= 0) {
+		width = 0;
+	} else {
+		if (flag[FLAG_MINUS_LJUSTIFY]) { /* padding on right */
+			++npc;
+			*pdrvr++ = 7;
+			*ppc = width;
+			*++ppc = (INT_OR_PTR)("");
+			width = 0;
+		} else if (flag[FLAG_0_PAD] == '0') { /* 0 padding */
+			pc_fwi[2] += width;
+			width = 0;
+		}
+	}
+	*drvr = 7;
+	ppc = pc_fwi;
+	*ppc++ = width + i;
+	*ppc = (INT_OR_PTR) sign_str;
+
+	pdrvr = drvr;
+	ppc = pc_fwi;
+	cnt = 0;
+	for (i=0 ; i<npc ; i++) {
+#if 1
+		fnprintf(fp, size, fmts[(int)(*pdrvr++)], (INT_OR_PTR)(*(ppc)), 
+				 (INT_OR_PTR)(*(ppc+1)));
 #else
-	*pos = '.';
+		j = fnprintf(fp, size, fmts[(int)(*pdrvr++)], (INT_OR_PTR)(*(ppc)), 
+					  (INT_OR_PTR)(*(ppc+1)));
+		assert(j == *ppc);
 #endif
-
- DO_SIGN:
-	if (negative) {
-		*--pos = '-';
+		if (size > *ppc) {
+			size -= *ppc;
+		}
+		cnt += *ppc;			/* to avoid problems if j == -1 */
+		ppc += 2;
 	}
 
-	return pos;
+	return cnt;
 }
+
+
+
+
+
+

+ 11 - 2
libc/stdio/Makefile

@@ -24,6 +24,15 @@ TOPDIR=../
 include $(TOPDIR)Rules.mak
 LIBC=$(TOPDIR)libc.a
 
+PRINTF_FLAGS =
+
+ifeq ($(HAS_FLOATS),true)
+	PRINTF_FLAGS += -DWANT_DOUBLE
+endif
+
+ifeq ($(HAS_LONG_LONG),true)
+	PRINTF_FLAGS += -DWANT_LONG_LONG
+endif
 
 MSRC=stdio.c
 MOBJ=_stdio_init.o fputc.o fgetc.o fflush.o fgets.o gets.o fputs.o	\
@@ -32,7 +41,7 @@ MOBJ=_stdio_init.o fputc.o fgetc.o fflush.o fgets.o gets.o fputs.o	\
 
 MSRC2=printf.c
 MOBJ2=printf.o sprintf.o fprintf.o vprintf.o vsprintf.o vfprintf.o snprintf.o \
-     vsnprintf.o asprintf.o vfnprintf.o
+     vsnprintf.o asprintf.o vfnprintf.o fnprintf.o
 
 MSRC3=scanf.c
 MOBJ3=scanf.o sscanf.o fscanf.o vscanf.o vsscanf.o vfscanf.o
@@ -54,7 +63,7 @@ $(MOBJ): $(MSRC)
 	$(STRIPTOOL) -x -R .note -R .comment $*.o
 
 $(MOBJ2): $(MSRC2)
-	$(CC) $(CFLAGS) -DL_$* $< -c -o $*.o
+	$(CC) $(CFLAGS) $(PRINTF_FLAGS) -DL_$* $< -c -o $*.o
 	$(STRIPTOOL) -x -R .note -R .comment $*.o
 
 $(MOBJ3): $(MSRC3)

+ 33 - 5
libc/stdio/printf.c

@@ -64,10 +64,16 @@
  *    call vsnprintf which allocates a fake file on the stack.
  * Removed WANT_FPUTC option.  Always use standard putc macro to avoid
  *    problems with the fake file used by the *s*printf functions.
- * Added asprintf.
+ * Fixed bug parsing flags -- did not restart scan.
+ * Added function asprintf.
  * Fixed 0-pad prefixing bug.
  * Converted sizeof(int) == sizeof(long) tests to compile time vs run time.
  *    This saves 112 bytes of code on i386.
+ * Fixed precision bug -- when negative set to default.
+ * Added function fnprintf to support __dtostr.
+ * Added floating point support for doubles.  Yeah!
+ * 
+ * TODO: long double support
  */
 
 /*****************************************************************************/
@@ -76,12 +82,15 @@
 /* The optional support for long longs and doubles comes in two forms.
  *
  *   1) Normal (or partial for doubles) output support.  Set to 1 to turn on.
- *      Adds about 70 bytes for doubles, about 220 bytes for long longs,
+ *      Adds about 130 bytes for doubles, about 220 bytes for long longs,
  *      and about 275 for both to the base code size of 1163 on i386.
  */
 
+/* These are now set in the Makefile based on Config. */
+/*
 #define WANT_LONG_LONG         0
 #define WANT_DOUBLE            0
+*/
 
 /*   2) An error message is inserted into the stream, an arg of the
  *      appropriate size is removed from the arglist, and processing
@@ -209,6 +218,19 @@ int fprintf(FILE * fp, const char *fmt, ...)
 }
 #endif
 
+#ifdef L_fnprintf
+int fnprintf(FILE * fp, size_t size, const char *fmt, ...)
+{
+	va_list ptr;
+	int rv;
+
+	va_strt(ptr, fmt);
+	rv = vfnprintf(fp, size, fmt, ptr);
+	va_end(ptr);
+	return rv;
+}
+#endif
+
 #ifdef L_vprintf
 int vprintf(const char *fmt, va_list ap)
 {
@@ -263,7 +285,8 @@ extern char *__ultostr(char *buf, unsigned long uval, int base, int uppercase);
 extern char *__ltostr(char *buf, long val, int base, int uppercase);
 extern char *__ulltostr(char *buf, unsigned long long uval, int base, int uppercase);
 extern char *__lltostr(char *buf, long long val, int base, int uppercase);
-extern char *__dtostr(char *buf, double x);
+extern int __dtostr(FILE * fp, size_t size, double x,
+				  char flag[], int width, int preci, char mode);
 
 enum {
 	FLAG_PLUS = 0,
@@ -379,7 +402,7 @@ int vfnprintf(FILE * op, size_t max_size, const char *fmt, va_list ap)
 				if (dpoint) {
 					preci = i;
 					if (i<0) {
-						preci = 0;
+						preci = -5;
 					}
 				} else {
 					width = i;
@@ -512,7 +535,12 @@ int vfnprintf(FILE * op, size_t max_size, const char *fmt, va_list ap)
 				} else if (p-u_spec < 27) {		/* floating point */
 #endif /* WANT_DOUBLE || WANT_DOUBLE_ERROR */
 #if WANT_DOUBLE
-					p = __dtostr(tmp + sizeof(tmp) - 1, va_arg(ap, double));
+					if (preci < 0) {
+						preci = 6;
+					}
+					cnt += __dtostr(op, max_size, va_arg(ap, double),
+									flag, width,  preci, *fmt);
+					goto nextfmt;
 #elif WANT_DOUBLE_ERROR
 					(void) va_arg(ap,double); /* carry on */
 					p = (char *) dbl_err;