| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 | /* * Copyright (C) 2000, 2001 Manuel Novoa III * * Function:  int __dtostr(FILE * fp, size_t size, long double x,  *			               char flag[], int width, int preci, char mode) * * This was written for uClibc to provide floating point support for * the printf functions.  It handles +/- infinity and nan on i386. * * Notes: * * At most MAX_DIGITS significant digits are kept.  Any trailing digits * are treated as 0 as they are really just the results of rounding noise * anyway.  If you want to do better, use an arbitary precision arithmetic * package.  ;-) * * 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. * * 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. *//*****************************************************************************//* Don't change anything that follows unless you know what you're doing.     *//*****************************************************************************//* * Configuration for the scaling power table.  Ignoring denormals, you * should have 2**EXP_TABLE_SIZE >= LDBL_MAX_EXP >= 2**(EXP_TABLE_SIZE-1). * The minimum for standard C is 6.  For IEEE 8bit doubles, 9 suffices. * For long doubles on i386, use 13. */#define EXP_TABLE_SIZE       13/*  * Set this to the maximum number of digits you want converted. * Conversion is done in blocks of DIGITS_PER_BLOCK (9 by default) digits. * (20) 17 digits suffices to uniquely determine a (long) double on i386. */#define MAX_DIGITS          20/* * 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 * calculations, it doesn't appear to suffer from the excess precision problem * caused by the FPU that strtod had.  If it causes problems, call the function * and compile zoicheck.c with -ffloat-store. */#define _zero_or_inf_check(x) ( x == (x/4) )/* * Fairly portable nan check.  Bitwise for i386 generated larger code. * If you have a better version, comment this out. */#define isnan(x) (x != 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. */#if EXP_TABLE_SIZE < 6#error EXP_TABLE_SIZE should be at least 6 to comply with standards#endif#define EXP_TABLE_MAX      (1U<<(EXP_TABLE_SIZE-1))/* * Only bother checking if this is too small. */#if LDBL_MAX_10_EXP/2 > EXP_TABLE_MAX#error larger EXP_TABLE_SIZE needed#endif/* * With 32 bit ints, we can get 9 digits per block. */#define DIGITS_PER_BLOCK     9#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/* 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)/* extra space for '-', '.', 'e+###', and nul */#define BUF_SIZE  ( 3 + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK )/*****************************************************************************/static const char *fmts[] = {	"%0*d", "%.*s", ".", "inf", "INF", "nan", "NAN", "%*s"};/*****************************************************************************/int __dtostr(FILE * fp, size_t size, long double x, 			 char flag[], int width, int preci, char mode){	long double exp_table[EXP_TABLE_SIZE];	long double p10;	DIGIT_BLOCK_TYPE digit_block; /* int of at least 32 bits */	int i, j;	int round, o_exp;	int exp, exp_neg;	char *s;	char *e;	char buf[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 */		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 = -1;		goto GENERATE_DIGITS;	}	if (x < 0) {				/* convert negatives to positives */		*sign_str = '-';		x = -x;	}	if (_zero_or_inf_check(x)) { /* must be inf since zero handled above */		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 */	for (i = 0, p10 = 10 ; i < EXP_TABLE_SIZE ; i++) {		exp_table[i] = p10;		p10 *= p10;	}	exp_neg = 0;	if (x < 1e8) {				/* do we need to scale up or down? */		exp_neg = 1;	}	exp = DIGITS_PER_BLOCK - 1;	i = EXP_TABLE_SIZE;	j = EXP_TABLE_MAX;	while ( i-- ) {				/* scale x such that 1e8 <= x < 1e9 */		if (exp_neg) {			if (x * exp_table[i] < 1e9) {				x *= exp_table[i];				exp -= j;			}		} else {			if (x / exp_table[i] >= 1e8) {				x /= exp_table[i];				exp += j;			}		}		j >>= 1;	}	if (x >= 1e9) {				/* handle bad rounding case */		x /= 10;		++exp;	}	assert(x < 1e9); GENERATE_DIGITS:	s = buf + 2; /* leave space for '\0' and '0' */	for (i = 0 ; i < NUM_DIGIT_BLOCKS ; ++i ) {		digit_block = (DIGIT_BLOCK_TYPE) x;		x = (x - digit_block) * 1e9;		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;	}	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;		}	}	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';	}	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;		}		o_exp = -1;	}	if (flag[FLAG_HASH] || (i) || ((o_mode != 'g') && (preci > 0))) {		*pdrvr++ = 2;			/* need decimal */		*ppc++ = 1;				/* needed for width calc */		ppc++;	}	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;		}	}	/* build exponent string */	if (mode != 'f') {		*pdrvr++ = 1;		*ppc++ = sprintf(exp_buf,"%c%+.2d", *exp_buf, exp);		*ppc++ = (INT_OR_PTR) exp_buf;	} 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		j = fnprintf(fp, size, fmts[(int)(*pdrvr++)], (INT_OR_PTR)(*(ppc)), 					  (INT_OR_PTR)(*(ppc+1)));		assert(j == *ppc);#endif		if (size > *ppc) {			size -= *ppc;		}		cnt += *ppc;			/* to avoid problems if j == -1 */		ppc += 2;	}	return cnt;}
 |