#include #include #include #include #include #include #include #define _MAX_CHARS 512 static char *lcset = "0123456789abcdef"; static struct p { double pvalue, nvalue; int exp; } powers[] = { { 1e32, 1e-32, 32}, { 1e16, 1e-16, 16}, { 1e8, 1e-8, 8}, { 1e4, 1e-4, 4}, { 1e2, 1e-2, 2}, { 1e1, 1e-1, 1 }, { 1e0, 1e-0, 0 } }; #define _MAX_PREC 16 static char _DEFUN(nextdigit,(value), double *value) { double tmp; *value = modf (*value * 10, &tmp) ; return lcset[(int)tmp]; } static char * _DEFUN(print_nan,(buffer, value, precision), char *buffer _AND double value _AND int precision) { size_t i; if (isnan(value)) { strcpy(buffer, "nan"); i = 3; } else { strcpy(buffer, "infinity"); i = 8; } while (i < precision) { buffer[i++] = ' '; } buffer[i++] = 0; return buffer; } /* A convert info struct */ typedef struct { char *buffer ; /* Destination of conversion */ double value; /* scratch Value to convert */ double original_value; /* saved Value to convert */ int value_neg; /* OUT: 1 if value initialiy neg */ int abs_exp; /* abs Decimal exponent of value */ int abs_exp_sign; /* + or - */ int exp; /* exp not sgned */ int type; /* fFeEgG used in printing before exp */ int print_trailing_zeros; /* Print 00's after a . */ int null_idx; /* Index of the null at the end */ /* These ones are read only */ int decimal_places; /* the number of digits to print after the decimal */ int max_digits; /* total number of digits to print */ int buffer_size; /* Size of output buffer */ /* Two sorts of dot ness. 0 never ever print a dot 1 print a dot if followed by a digit 2 always print a dot, even if no digit following */ enum { dot_never, dot_sometimes, dot_always} dot; /* Print a decimal point, always */ int dot_idx; /* where the dot went, or would have gone */ } cvt_info_type; void _DEFUN(renormalize,(in), cvt_info_type *in) { /* Make sure all numbers are less than 1 */ while (in->value >= 1.0) { in->value = in->value * 0.1; in->exp++; } /* Now we have only numbers between 0 and .9999.., and have adjusted exp to account for the shift */ if (in->exp >= 0) { in->abs_exp_sign = '+'; in->abs_exp = in->exp; } else { in->abs_exp_sign = '-'; in->abs_exp = -in->exp; } } /* This routine looks at original_value, and makes it between 0 and 1, modifying exp as it goes */ static void _DEFUN(normalize,(value, in), double value _AND cvt_info_type *in) { int j; int texp; if (value != 0) { texp = -1; if (value < 0.0) { in->value_neg =1 ; value = - value; } else { in->value_neg = 0; } /* Work out texponent & normalise value */ /* If value > 1, then shrink it */ if (value >= 1.0) { for (j = 0; j < 6; j++) { while (value >= powers[j].pvalue) { value /= powers[j].pvalue; texp += powers[j].exp; } } } else if (value != 0.0) { for (j = 0; j < 6; j++) { while (value <= powers[j].nvalue) { value *= powers[j].pvalue; texp -= powers[j].exp; } } } } else { texp = 0; } in->exp = texp; in->value = value; in->original_value = value; renormalize(in); } int _DEFUN(round,(in, start, now, ch), cvt_info_type *in _AND char *start _AND char *now _AND char ch) { double rounder = 5.0; char *p; int ok = 0; now --; /* If the next digit to output would have been a '5' run back and */ /* see if we can create a more rounded number. If we can then do it. If not (like when the number was 9.9 and the last char was another 9), then we'll have to modify the number and try again */ if (ch < '5') return 0; for (p = now;!ok && p >= start; p--) { switch (*p) { default: abort(); case '.': break; case '9': rounder = rounder * 0.1; break; case '8': case '7': case '6': case '5': case '4': case '3': case '2': case '1': case '0': p = now; while (1) { if (*p == '9') { *p = '0'; } else if (*p != '.') { (*p)++; return 0; } p--; } } } /* Getting here means that we couldn't round the number in place textually - there have been all nines. We'll have to add to it and try the conversion again eg .99999[9] can't be rounded in place, so add .000005 to it giving: 1.000004 we notice that the result is > 1 so add to exp and divide by 10 .100004 */ in->original_value = in->value = in->original_value + rounder; normalize(in->original_value , in); return 1; } void _DEFUN(_cvte,(in), register cvt_info_type *in) { int buffer_idx =0; int digit = 0; int after_decimal =0; in->buffer[buffer_idx++] = nextdigit(&(in->value)); digit++; in->dot_idx = buffer_idx; switch (in->dot) { case dot_never: break; case dot_sometimes: if (in->decimal_places && digit < in->max_digits) { in->buffer[buffer_idx++] = '.'; } break; case dot_always: in->buffer[buffer_idx++] = '.'; } while (buffer_idx < in->buffer_size && after_decimal < in->decimal_places && digit < in->max_digits) { in->buffer[buffer_idx] = nextdigit(&(in->value)); after_decimal++; buffer_idx++; digit++; } if (round(in, in->buffer, in->buffer+buffer_idx, nextdigit(&(in->value)))) { _cvte(in); } else { in->buffer[buffer_idx++] = in->type; in->buffer[buffer_idx++] = in->abs_exp_sign; if (in->abs_exp >= 100) { in->buffer[buffer_idx++] = lcset[in->abs_exp / 100]; in->abs_exp %= 100; } in->buffer[buffer_idx++] = lcset[in->abs_exp / 10]; in->buffer[buffer_idx++] = lcset[in->abs_exp % 10]; } in->buffer[buffer_idx++] = 0; } /* Produce NNNN.FFFF */ void _DEFUN(_cvtf,(in), cvt_info_type *in) { int buffer_idx = 0; /* Current char being output */ int after_decimal = 0; int digit =0; in->dot_idx = in->exp + 1; /* Two sorts of number, NNN.FFF and 0.0000...FFFF */ /* Print all the digits up to the decimal point */ while (buffer_idx <= in->exp && digit < in->max_digits && buffer_idx < in->buffer_size) { in->buffer[buffer_idx] = nextdigit(&(in->value)); buffer_idx++; digit ++; } /* And the decimal point if we should */ if (buffer_idx < in->buffer_size) { switch (in->dot) { case dot_never: break; case dot_sometimes: /* Only print a dot if following chars */ if (in->decimal_places && digit < in->max_digits ) { in->buffer[buffer_idx++] = '.'; } break; case dot_always: in->buffer[buffer_idx++] = '.'; } after_decimal = 0; /* And the digits following the point if necessary */ /* Only print the leading zeros if a dot was possible */ if (in->dot || in->exp>0) { while (buffer_idx < in->buffer_size && (in->abs_exp_sign == '-' && digit < in->abs_exp - 1) && (after_decimal < in->decimal_places) && (digit < in->max_digits)) { in->buffer[buffer_idx] = '0'; buffer_idx++; digit++; after_decimal++; } } while (buffer_idx < in->buffer_size && after_decimal < in->decimal_places && digit < in->max_digits) { in->buffer[buffer_idx] = nextdigit(&(in->value)); buffer_idx++; digit++; after_decimal++; } } in->null_idx = buffer_idx; in->buffer[buffer_idx] = 0; if (round(in, in->buffer, in->buffer+buffer_idx, nextdigit(&(in->value)))) { _cvtf(in); } } char * _DEFUN(_dcvt,(buffer, invalue, precision, width, type, dot), char *buffer _AND double invalue _AND int precision _AND int width _AND char type _AND int dot) { cvt_info_type in; in.buffer = buffer; in.buffer_size = 512; if (!finite(invalue)) { return print_nan(buffer, invalue, precision); } normalize(invalue, &in); in.type = type; in.dot = dot? dot_always: dot_sometimes; switch (type) { case 'g': case 'G': /* When formatting a g, the precision refers to the number of char positions *total*, this leads to various off by ones */ { /* A precision of 0 means 1 */ if (precision == 0) precision = 1; /* A g turns into an e if there are more digits than the precision, or it's smaller than e-4 */ if (in.exp >= precision || in.exp < -4) { in.type = (type == 'g' ? 'e' : 'E'); in.decimal_places = _MAX_CHARS; in.max_digits = precision; in.print_trailing_zeros = 1; _cvte(&in); } else { /* G means total number of chars to print */ in.decimal_places = _MAX_CHARS; in.max_digits = precision; in.type = (type == 'g' ? 'f' : 'F'); in.print_trailing_zeros = 0; _cvtf(&in); if (!dot) { /* trim trailing zeros */ int j = in.null_idx -1; while (j > 0 && in.buffer[j] == '0') { in.buffer[j] = 0; j--; } /* Stamp on a . if not followed by zeros */ if (j > 0 && buffer[j] == '.') in.buffer[j] = 0; } } break; case 'f': case 'F': in.decimal_places= precision; in.max_digits = _MAX_CHARS; in.print_trailing_zeros = 1; _cvtf(&in); break; case 'e': case 'E': in.print_trailing_zeros = 1; in.decimal_places = precision; in.max_digits = _MAX_CHARS; _cvte(&in); break; } } return buffer; } char * _DEFUN(fcvtbuf,(invalue,ndigit,decpt,sign, fcvt_buf), double invalue _AND int ndigit _AND int *decpt _AND int *sign _AND char *fcvt_buf) { cvt_info_type in; in.buffer = fcvt_buf; in.buffer_size = 512; if (!finite(invalue)) { return print_nan(fcvt_buf, invalue, ndigit); } normalize(invalue, &in); in.dot = dot_never; /* Don't print a decimal point */ in.max_digits = _MAX_CHARS; in.buffer_size = _MAX_CHARS; /* Take as many as needed */ in.decimal_places = ndigit; _cvtf(&in); *decpt = in.dot_idx; *sign = in.value_neg; return in.buffer; } char * _DEFUN(ecvtbuf,(invalue,ndigit,decpt,sign, fcvt_buf), double invalue _AND int ndigit _AND int *decpt _AND int *sign _AND char *fcvt_buf) { cvt_info_type in; in.buffer = fcvt_buf; if (!finite(invalue)) { return print_nan(fcvt_buf, invalue, ndigit); } normalize(invalue, &in); in.dot = dot_never; /* Don't print a decimal point */ /* We can work out how many digits go after the decimal point */ in.buffer_size =_MAX_CHARS; in.decimal_places = _MAX_CHARS; in.max_digits = ndigit; /* Take as many as told */ _cvtf(&in); *decpt = in.dot_idx; *sign = in.value_neg; return in.buffer; } char * _DEFUN(gcvt,(d,ndigit,buf), double d _AND int ndigit _AND char *buf) { return _dcvt(buf, d, ndigit, 0, 'g', 1); }