/* * 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; }