commit f8955f181efa175573ccecc71a19464fdba50ef5 from: rsc date: Mon May 22 14:53:35 2006 UTC new, commented fltfmt commit - fd745e874891a9001e6774c26e145984b9db147c commit + f8955f181efa175573ccecc71a19464fdba50ef5 blob - 8288a9667f8875c3bfd54c3891d5b5751eed5378 blob + 09e8b443d58cf9cbceff90c54975314eb1152a59 --- src/lib9/fmt/fltfmt.c +++ src/lib9/fmt/fltfmt.c @@ -6,11 +6,12 @@ #include #include #include -#include #include +#include #include "plan9.h" #include "fmt.h" #include "fmtdef.h" +#include "nan.h" enum { @@ -42,9 +43,9 @@ static double pows10[] = 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159, }; +#define npows10 ((int)(sizeof(pows10)/sizeof(pows10[0]))) +#define pow10(x) fmtpow10(x) -#define pow10(x) fmtpow10(x) - static double pow10(int n) { @@ -53,330 +54,615 @@ pow10(int n) neg = 0; if(n < 0){ - if(n < DBL_MIN_10_EXP){ + if(n < DBL_MIN_10_EXP) return 0.; - } neg = 1; n = -n; - }else if(n > DBL_MAX_10_EXP){ + }else if(n > DBL_MAX_10_EXP) return HUGE_VAL; - } - if(n < (int)(sizeof(pows10)/sizeof(pows10[0]))) + + if(n < npows10) d = pows10[n]; else{ - d = pows10[sizeof(pows10)/sizeof(pows10[0]) - 1]; + d = pows10[npows10-1]; for(;;){ - n -= sizeof(pows10)/sizeof(pows10[0]) - 1; - if(n < (int)(sizeof(pows10)/sizeof(pows10[0]))){ + n -= npows10 - 1; + if(n < npows10){ d *= pows10[n]; break; } - d *= pows10[sizeof(pows10)/sizeof(pows10[0]) - 1]; + d *= pows10[npows10 - 1]; } } - if(neg){ + if(neg) return 1./d; - } return d; } +/* + * add 1 to the decimal integer string a of length n. + * if 99999 overflows into 10000, return 1 to tell caller + * to move the virtual decimal point. + */ static int -xadd(char *a, int n, int v) +xadd1(char *a, int n) { char *b; int c; - if(n < 0 || n >= NSIGNIF) + if(n < 0 || n > NSIGNIF) return 0; - for(b = a+n; b >= a; b--) { - c = *b + v; + for(b = a+n-1; b >= a; b--) { + c = *b + 1; if(c <= '9') { *b = c; return 0; } *b = '0'; - v = 1; } - *a = '1'; /* overflow adding */ + /* + * need to overflow adding digit. + * shift number down and insert 1 at beginning. + * decimal is known to be 0s or we wouldn't + * have gotten this far. (e.g., 99999+1 => 00000) + */ + a[0] = '1'; return 1; } +/* + * subtract 1 from the decimal integer string a. + * if 10000 underflows into 09999, make it 99999 + * and return 1 to tell caller to move the virtual + * decimal point. this way, xsub1 is inverse of xadd1. + */ static int -xsub(char *a, int n, int v) +xsub1(char *a, int n) { char *b; int c; - for(b = a+n; b >= a; b--) { - c = *b - v; + if(n < 0 || n > NSIGNIF) + return 0; + for(b = a+n-1; b >= a; b--) { + c = *b - 1; if(c >= '0') { + if(c == '0' && b == a) { + /* + * just zeroed the top digit; shift everyone up. + * decimal is known to be 9s or we wouldn't + * have gotten this far. (e.g., 10000-1 => 09999) + */ + *b = '9'; + return 1; + } *b = c; return 0; } *b = '9'; - v = 1; } - *a = '9'; /* underflow subtracting */ - return 1; + /* + * can't get here. the number a is always normalized + * so that it has a nonzero first digit. + */ + abort(); } +/* + * format exponent like sprintf(p, "e%+02d", e) + */ static void -xdtoa(Fmt *fmt, char *s2, double f) +xfmtexp(char *p, int e, int ucase) { - char s1[NSIGNIF+10]; - double g, h; - int e, d, i, n; - int c1, c2, c3, c4, ucase, sign, chr, prec; + char se[9]; + int i; - prec = FDEFLT; - if(fmt->flags & FmtPrec) - prec = fmt->prec; - if(prec > FDIGIT) - prec = FDIGIT; - if(__isNaN(f)) { - strcpy(s2, "NaN"); - return; + *p++ = ucase ? 'E' : 'e'; + if(e < 0) { + *p++ = '-'; + e = -e; + } else + *p++ = '+'; + i = 0; + while(e) { + se[i++] = e % 10 + '0'; + e /= 10; } - if(__isInf(f, 1)) { - strcpy(s2, "+Inf"); - return; - } - if(__isInf(f, -1)) { - strcpy(s2, "-Inf"); - return; - } - sign = 0; + while(i < 2) + se[i++] = '0'; + while(i > 0) + *p++ = se[--i]; + *p++ = '\0'; +} + +/* + * compute decimal integer m, exp such that: + * f = m*10^exp + * m is as short as possible with losing exactness + * assumes special cases (NaN, +Inf, -Inf) have been handled. + */ +static void +xdtoa(double f, char *s, int *exp, int *neg, int *ns) +{ + int c, d, e2, e, ee, i, ndigit, oerrno; + char tmp[NSIGNIF+10]; + double g; + + oerrno = errno; /* in case strtod smashes errno */ + + /* + * make f non-negative. + */ + *neg = 0; if(f < 0) { f = -f; - sign++; + *neg = 1; } - ucase = 0; - chr = fmt->r; - if(isupper(chr)) { - ucase = 1; - chr = tolower(chr); - } - e = 0; - g = f; - if(g != 0) { - frexp(f, &e); - e = e * .301029995664; - if(e >= -150 && e <= +150) { - d = 0; - h = f; - } else { - d = e/2; - h = f * pow10(-d); - } - g = h * pow10(d-e); - while(g < 1) { - e--; - g = h * pow10(d-e); - } - while(g >= 10) { - e++; - g = h * pow10(d-e); - } - } - /* - * convert NSIGNIF digits and convert - * back to get accuracy. + * must handle zero specially. */ - for(i=0; i= NSIGNIF-2) { - strcpy(s2, s1); - d = e; - s1[NSIGNIF-2] = '0'; - s1[NSIGNIF-1] = '0'; - sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1); - g = strtod(s1, nil); - if(g == f) - goto found; - if(xadd(s1, NSIGNIF-3, 1)) { - e++; - sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1); - } - g = strtod(s1, nil); - if(g == f) - goto found; - strcpy(s1, s2); - e = d; + frexp(f, &e2); + e = (int)(e2 * .301029995664); + g = f * pow10(-e); + while(g < 1) { + e--; + g = f * pow10(-e); } + while(g >= 10) { + e++; + g = f * pow10(-e); + } /* - * convert back so s1 gets exact answer + * convert NSIGNIF digits as a first approximation. */ - for(;;) { - sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1); - g = strtod(s1, nil); + for(i=0; i g) { - if(xadd(s1, NSIGNIF-1, 1)) + if(xadd1(s, NSIGNIF)) { + /* gained a digit */ e--; + xfmtexp(s+NSIGNIF, e, 0); + } continue; } if(f < g) { - if(xsub(s1, NSIGNIF-1, 1)) + if(xsub1(s, NSIGNIF)) { + /* lost a digit */ e++; + xfmtexp(s+NSIGNIF, e, 0); + } continue; } break; } -found: /* - * sign + * play with the decimal to try to simplify. */ - d = 0; - i = 0; - if(sign) - s2[d++] = '-'; - else if(fmt->flags & FmtSign) - s2[d++] = '+'; - else if(fmt->flags & FmtSpace) - s2[d++] = ' '; /* - * copy into final place - * c1 digits of leading '0' - * c2 digits from conversion - * c3 digits of trailing '0' - * c4 digits after '.' + * bump last few digits up to 9 if we can */ - c1 = 0; - c2 = prec + 1; - c3 = 0; - c4 = prec; - switch(chr) { - default: - if(xadd(s1, c2, 5)) - e++; - break; - case 'g': - /* - * decide on 'e' of 'f' style convers - */ - if(xadd(s1, c2, 5)) - e++; - if(e >= -5 && e <= prec) { - c1 = -e - 1; - c4 = prec - e; - chr = 'h'; /* flag for 'f' style */ + for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) { + c = s[i]; + if(c != '9') { + s[i] = '9'; + g = strtod(s, nil); + if(g != f) { + s[i] = c; + break; + } } - break; - case 'f': - if(xadd(s1, c2+e, 5)) - e++; - c1 = -e; - if(c1 > prec) - c1 = c2; - c2 += e; - break; } /* - * clean up c1 c2 and c3 + * add 1 in hopes of turning 9s to 0s */ - if(c1 < 0) - c1 = 0; - if(c2 < 0) - c2 = 0; - if(c2 > NSIGNIF) { - c3 = c2-NSIGNIF; - c2 = NSIGNIF; + if(s[NSIGNIF-1] == '9') { + strcpy(tmp, s); + ee = e; + if(xadd1(tmp, NSIGNIF)) { + ee--; + xfmtexp(tmp+NSIGNIF, ee, 0); + } + g = strtod(tmp, nil); + if(g == f) { + strcpy(s, tmp); + e = ee; + } } - + /* - * copy digits + * bump last few digits down to 0 as we can. */ - while(c1 > 0) { - if(c1+c2+c3 == c4) - s2[d++] = '.'; - s2[d++] = '0'; - c1--; + for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) { + c = s[i]; + if(c != '0') { + s[i] = '0'; + g = strtod(s, nil); + if(g != f) { + s[i] = c; + break; + } + } } - while(c2 > 0) { - if(c2+c3 == c4) - s2[d++] = '.'; - s2[d++] = s1[i++]; - c2--; - } - while(c3 > 0) { - if(c3 == c4) - s2[d++] = '.'; - s2[d++] = '0'; - c3--; - } /* - * strip trailing '0' on g conv + * remove trailing zeros. */ - if(fmt->flags & FmtSharp) { - if(0 == c4) - s2[d++] = '.'; - } else - if(chr == 'g' || chr == 'h') { - for(n=d-1; n>=0; n--) - if(s2[n] != '0') - break; - for(i=n; i>=0; i--) - if(s2[i] == '.') { - d = n; - if(i != n) - d++; - break; + ndigit = NSIGNIF; + while(ndigit > 1 && s[ndigit-1] == '0'){ + e++; + --ndigit; + } + s[ndigit] = 0; + *exp = e; + *ns = ndigit; + errno = oerrno; +} + +#ifdef PLAN9PORT +static char *special[] = { "NaN", "NaN", "+Inf", "+Inf", "-Inf", "-Inf" }; +#else +static char *special[] = { "nan", "NAN", "inf", "INF", "-inf", "-INF" }; +#endif + +int +__efgfmt(Fmt *fmt) +{ + char buf[NSIGNIF+10], *dot, *digits, *p, *s, suf[10], *t; + double f; + int c, chr, dotwid, e, exp, fl, ndigits, neg, newndigits; + int pad, point, prec, realchr, sign, sufwid, ucase, wid, z1, z2; + Rune r, *rs, *rt; + + f = va_arg(fmt->args, double); + + /* + * extract formatting flags + */ + fl = fmt->flags; + fmt->flags = 0; + prec = FDEFLT; + if(fl & FmtPrec) + prec = fmt->prec; + chr = fmt->r; + ucase = 0; + switch(chr) { + case 'A': + case 'E': + case 'F': + case 'G': + chr += 'a'-'A'; + ucase = 1; + break; + } + + /* + * pick off special numbers. + */ + if(__isNaN(f)) { + s = special[0+ucase]; + special: + fmt->flags = fl & (FmtWidth|FmtLeft); + return __fmtcpy(fmt, s, strlen(s), strlen(s)); + } + if(__isInf(f, 1)) { + s = special[2+ucase]; + goto special; + } + if(__isInf(f, -1)) { + s = special[4+ucase]; + goto special; + } + + /* + * get exact representation. + */ + digits = buf; + xdtoa(f, digits, &exp, &neg, &ndigits); + + /* + * get locale's decimal point. + */ + dot = fmt->decimal; + if(dot == nil) + dot = "."; + dotwid = utflen(dot); + + /* + * now the formatting fun begins. + * compute parameters for actual fmt: + * + * pad: number of spaces to insert before/after field. + * z1: number of zeros to insert before digits + * z2: number of zeros to insert after digits + * point: number of digits to print before decimal point + * ndigits: number of digits to use from digits[] + * suf: trailing suffix, like "e-5" + */ + realchr = chr; + switch(chr){ + case 'g': + /* + * convert to at most prec significant digits. (prec=0 means 1) + */ + if(prec == 0) + prec = 1; + if(ndigits > prec) { + if(digits[prec] >= '5' && xadd1(digits, prec)) + exp++; + exp += ndigits-prec; + ndigits = prec; + } + + /* + * extra rules for %g (implemented below): + * trailing zeros removed after decimal unless FmtSharp. + * decimal point only if digit follows. + */ + + /* fall through to %e */ + default: + case 'e': + /* + * one significant digit before decimal, no leading zeros. + */ + point = 1; + z1 = 0; + + /* + * decimal point is after ndigits digits right now. + * slide to be after first. + */ + e = exp + (ndigits-1); + + /* + * if this is %g, check exponent and convert prec + */ + if(realchr == 'g') { + if(-4 <= e && e < prec) + goto casef; + prec--; /* one digit before decimal; rest after */ + } + + /* + * compute trailing zero padding or truncate digits. + */ + if(1+prec >= ndigits) + z2 = 1+prec - ndigits; + else { + /* + * truncate digits + */ + assert(realchr != 'g'); + newndigits = 1+prec; + if(digits[newndigits] >= '5' && xadd1(digits, newndigits)) { + /* + * had 999e4, now have 100e5 + */ + e++; } + ndigits = newndigits; + z2 = 0; + } + xfmtexp(suf, e, ucase); + sufwid = strlen(suf); + break; + + casef: + case 'f': + /* + * determine where digits go with respect to decimal point + */ + if(ndigits+exp > 0) { + point = ndigits+exp; + z1 = 0; + } else { + point = 1; + z1 = 1 + -(ndigits+exp); + } + + /* + * %g specifies prec = number of significant digits + * convert to number of digits after decimal point + */ + if(realchr == 'g') + prec += z1 - point; + + /* + * compute trailing zero padding or truncate digits. + */ + if(point+prec >= z1+ndigits) + z2 = point+prec - (z1+ndigits); + else { + /* + * truncate digits + */ + assert(realchr != 'g'); + newndigits = point+prec - z1; + if(newndigits < 0) { + z1 += newndigits; + newndigits = 0; + } else if(newndigits == 0) { + /* perhaps round up */ + if(digits[0] >= '5'){ + digits[0] = '1'; + newndigits = 1; + goto newdigit; + } + } else if(digits[newndigits] >= '5' && xadd1(digits, newndigits)) { + /* + * digits was 999, is now 100; make it 1000 + */ + digits[newndigits++] = '0'; + newdigit: + /* + * account for new digit + */ + if(z1) /* 0.099 => 0.100 or 0.99 => 1.00*/ + z1--; + else /* 9.99 => 10.00 */ + point++; + } + z2 = 0; + ndigits = newndigits; + } + sufwid = 0; + break; } - if(chr == 'e' || chr == 'g') { - if(ucase) - s2[d++] = 'E'; - else - s2[d++] = 'e'; - c1 = e; - if(c1 < 0) { - s2[d++] = '-'; - c1 = -c1; - } else - s2[d++] = '+'; - if(c1 >= 100) { - s2[d++] = c1/100 + '0'; - c1 = c1%100; + + /* + * if %g is given without FmtSharp, remove trailing zeros. + * must do after truncation, so that e.g. print %.3g 1.001 + * produces 1, not 1.00. sorry, but them's the rules. + */ + if(realchr == 'g' && !(fl & FmtSharp)) { + if(z1+ndigits+z2 >= point) { + if(z1+ndigits < point) + z2 = point - (z1+ndigits); + else{ + z2 = 0; + while(z1+ndigits > point && digits[ndigits-1] == '0') + ndigits--; + } } - s2[d++] = c1/10 + '0'; - s2[d++] = c1%10 + '0'; } - s2[d] = 0; -} -static int -floatfmt(Fmt *fmt, double f) -{ - char s[341]; /* precision+exponent+sign+'.'+null */ + /* + * compute width of all digits and decimal point and suffix if any + */ + wid = z1+ndigits+z2; + if(wid > point) + wid += dotwid; + else if(wid == point){ + if(fl & FmtSharp) + wid += dotwid; + else + point++; /* do not print any decimal point */ + } + wid += sufwid; - xdtoa(fmt, s, f); - fmt->flags &= FmtWidth|FmtLeft; - __fmtcpy(fmt, s, strlen(s), strlen(s)); - return 0; -} + /* + * determine sign + */ + sign = 0; + if(neg) + sign = '-'; + else if(fl & FmtSign) + sign = '+'; + else if(fl & FmtSpace) + sign = ' '; + if(sign) + wid++; -int -__efgfmt(Fmt *f) -{ - double d; + /* + * compute padding + */ + pad = 0; + if((fl & FmtWidth) && fmt->width > wid) + pad = fmt->width - wid; + if(pad && !(fl & FmtLeft) && (fl & FmtZero)){ + z1 += pad; + point += pad; + pad = 0; + } - d = va_arg(f->args, double); - return floatfmt(f, d); + /* + * format the actual field. too bad about doing this twice. + */ + if(fmt->runes){ + if(pad && !(fl & FmtLeft) && __rfmtpad(fmt, pad) < 0) + return -1; + rt = (Rune*)fmt->to; + rs = (Rune*)fmt->stop; + if(sign) + FMTRCHAR(fmt, rt, rs, sign); + while(z1>0 || ndigits>0 || z2>0) { + if(z1 > 0){ + z1--; + c = '0'; + }else if(ndigits > 0){ + ndigits--; + c = *digits++; + }else if(z2 > 0){ + z2--; + c = '0'; + } + FMTRCHAR(fmt, rt, rs, c); + if(--point == 0) { + for(p = dot; *p; ){ + p += chartorune(&r, p); + FMTRCHAR(fmt, rt, rs, r); + } + } + } + fmt->nfmt += rt - (Rune*)fmt->to; + fmt->to = rt; + if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0) + return -1; + if(pad && (fl & FmtLeft) && __rfmtpad(fmt, pad) < 0) + return -1; + }else{ + if(pad && !(fl & FmtLeft) && __fmtpad(fmt, pad) < 0) + return -1; + t = (char*)fmt->to; + s = (char*)fmt->stop; + if(sign) + FMTCHAR(fmt, t, s, sign); + while(z1>0 || ndigits>0 || z2>0) { + if(z1 > 0){ + z1--; + c = '0'; + }else if(ndigits > 0){ + ndigits--; + c = *digits++; + }else if(z2 > 0){ + z2--; + c = '0'; + } + FMTCHAR(fmt, t, s, c); + if(--point == 0) + for(p=dot; *p; p++) + FMTCHAR(fmt, t, s, *p); + } + fmt->nfmt += t - (char*)fmt->to; + fmt->to = t; + if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0) + return -1; + if(pad && (fl & FmtLeft) && __fmtpad(fmt, pad) < 0) + return -1; + } + return 0; } +