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