diff --git a/jsbuiltin.c b/jsbuiltin.c index 289bca4..6b57af6 100644 --- a/jsbuiltin.c +++ b/jsbuiltin.c @@ -74,7 +74,7 @@ static void jsB_parseFloat(js_State *J) else if (!strncmp(s, "-Infinity", 9)) js_pushnumber(J, -INFINITY); else { - n = js_strtod(s, &e); + n = js_stringtofloat(s, &e); if (e == s) js_pushnumber(J, NAN); else diff --git a/jsdtoa.c b/jsdtoa.c index c489261..9703af0 100644 --- a/jsdtoa.c +++ b/jsdtoa.c @@ -18,6 +18,10 @@ #include #include +#include "jsi.h" + +typedef unsigned long ulong; + enum { NSIGNIF = 17 }; /* @@ -44,7 +48,7 @@ static double pows10[] = 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) @@ -101,7 +105,7 @@ xadd1(char *a, int n) * 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) + * have gotten this far. (e.g., 99999+1 => 00000) */ a[0] = '1'; return 1; @@ -111,7 +115,7 @@ xadd1(char *a, int n) * 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. + * decimal point. this way, xsub1 is inverse of xadd1. */ static int xsub1(char *a, int n) @@ -128,7 +132,7 @@ xsub1(char *a, int n) /* * 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) + * have gotten this far. (e.g., 10000-1 => 09999) */ *b = '9'; return 1; @@ -139,7 +143,7 @@ xsub1(char *a, int n) *b = '9'; } /* - * can't get here. the number a is always normalized + * can't get here. the number a is always normalized * so that it has a nonzero first digit. */ return 0; @@ -149,7 +153,7 @@ xsub1(char *a, int n) * format exponent like sprintf(p, "e%+d", e) */ void -jsV_fmtexp(char *p, int e) +js_fmtexp(char *p, int e) { char se[9]; int i; @@ -179,7 +183,7 @@ jsV_fmtexp(char *p, int e) * assumes special cases (NaN, +Inf, -Inf) have been handled. */ void -jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) +js_dtoa(double f, char *s, int *exp, int *neg, int *ns) { int c, d, e2, e, ee, i, ndigit, oerrno; char tmp[NSIGNIF+10]; @@ -237,18 +241,18 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) * adjust e because s is 314159... not 3.14159... */ e -= NSIGNIF-1; - jsV_fmtexp(s+NSIGNIF, e); + js_fmtexp(s+NSIGNIF, e); /* * adjust conversion until strtod(s) == f exactly. */ for(i=0; i<10; i++) { - g = strtod(s, NULL); + g = js_strtod(s, NULL); if(f > g) { if(xadd1(s, NSIGNIF)) { /* gained a digit */ e--; - jsV_fmtexp(s+NSIGNIF, e); + js_fmtexp(s+NSIGNIF, e); } continue; } @@ -256,7 +260,7 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) if(xsub1(s, NSIGNIF)) { /* lost a digit */ e++; - jsV_fmtexp(s+NSIGNIF, e); + js_fmtexp(s+NSIGNIF, e); } continue; } @@ -274,7 +278,7 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) c = s[i]; if(c != '9') { s[i] = '9'; - g = strtod(s, NULL); + g = js_strtod(s, NULL); if(g != f) { s[i] = c; break; @@ -290,9 +294,9 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) ee = e; if(xadd1(tmp, NSIGNIF)) { ee--; - jsV_fmtexp(tmp+NSIGNIF, ee); + js_fmtexp(tmp+NSIGNIF, ee); } - g = strtod(tmp, NULL); + g = js_strtod(tmp, NULL); if(g == f) { strcpy(s, tmp); e = ee; @@ -306,7 +310,7 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) c = s[i]; if(c != '0') { s[i] = '0'; - g = strtod(s, NULL); + g = js_strtod(s, NULL); if(g != f) { s[i] = c; break; @@ -327,3 +331,513 @@ jsV_dtoa(double f, char *s, int *exp, int *neg, int *ns) *ns = ndigit; errno = oerrno; } + +static ulong +umuldiv(ulong a, ulong b, ulong c) +{ + double d; + + d = ((double)a * (double)b) / (double)c; + if(d >= 4294967295.) + d = 4294967295.; + return (ulong)d; +} + +/* + * This routine will convert to arbitrary precision + * floating point entirely in multi-precision fixed. + * The answer is the closest floating point number to + * the given decimal number. Exactly half way are + * rounded ala ieee rules. + * Method is to scale input decimal between .500 and .999... + * with external power of 2, then binary search for the + * closest mantissa to this decimal number. + * Nmant is is the required precision. (53 for ieee dp) + * Nbits is the max number of bits/word. (must be <= 28) + * Prec is calculated - the number of words of fixed mantissa. + */ +enum +{ + Nbits = 28, /* bits safely represented in a ulong */ + Nmant = 53, /* bits of precision required */ + Prec = (Nmant+Nbits+1)/Nbits, /* words of Nbits each to represent mantissa */ + Sigbit = 1<<(Prec*Nbits-Nmant), /* first significant bit of Prec-th word */ + Ndig = 1500, + One = (ulong)(1<>1), + Maxe = 310, + + Fsign = 1<<0, /* found - */ + Fesign = 1<<1, /* found e- */ + Fdpoint = 1<<2, /* found . */ + + S0 = 0, /* _ _S0 +S1 #S2 .S3 */ + S1, /* _+ #S2 .S3 */ + S2, /* _+# #S2 .S4 eS5 */ + S3, /* _+. #S4 */ + S4, /* _+#.# #S4 eS5 */ + S5, /* _+#.#e +S6 #S7 */ + S6, /* _+#.#e+ #S7 */ + S7 /* _+#.#e+# #S7 */ +}; + +static int xcmp(char*, char*); +static int fpcmp(char*, ulong*); +static void frnorm(ulong*); +static void divascii(char*, int*, int*, int*); +static void mulascii(char*, int*, int*, int*); + +typedef struct Tab Tab; +struct Tab +{ + int bp; + int siz; + char* cmp; +}; + +double +js_strtod(const char *as, char **aas) +{ + int na, ex, dp, bp, c, i, flag, state; + ulong low[Prec], hig[Prec], mid[Prec]; + double d; + char *s, a[Ndig]; + + flag = 0; /* Fsign, Fesign, Fdpoint */ + na = 0; /* number of digits of a[] */ + dp = 0; /* na of decimal point */ + ex = 0; /* exonent */ + + state = S0; + for(s=(char*)as;; s++) { + c = *s; + if(c >= '0' && c <= '9') { + switch(state) { + case S0: + case S1: + case S2: + state = S2; + break; + case S3: + case S4: + state = S4; + break; + + case S5: + case S6: + case S7: + state = S7; + ex = ex*10 + (c-'0'); + continue; + } + if(na == 0 && c == '0') { + dp--; + continue; + } + if(na < Ndig-50) + a[na++] = c; + continue; + } + switch(c) { + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + if(state == S0) + continue; + break; + case '-': + if(state == S0) + flag |= Fsign; + else + flag |= Fesign; + case '+': + if(state == S0) + state = S1; + else + if(state == S5) + state = S6; + else + break; /* syntax */ + continue; + case '.': + flag |= Fdpoint; + dp = na; + if(state == S0 || state == S1) { + state = S3; + continue; + } + if(state == S2) { + state = S4; + continue; + } + break; + case 'e': + case 'E': + if(state == S2 || state == S4) { + state = S5; + continue; + } + break; + } + break; + } + + /* + * clean up return char-pointer + */ + switch(state) { + case S0: + if(xcmp(s, "nan") == 0) { + if(aas != NULL) + *aas = s+3; + goto retnan; + } + case S1: + if(xcmp(s, "infinity") == 0) { + if(aas != NULL) + *aas = s+8; + goto retinf; + } + if(xcmp(s, "inf") == 0) { + if(aas != NULL) + *aas = s+3; + goto retinf; + } + case S3: + if(aas != NULL) + *aas = (char*)as; + goto ret0; /* no digits found */ + case S6: + s--; /* back over +- */ + case S5: + s--; /* back over e */ + break; + } + if(aas != NULL) + *aas = s; + + if(flag & Fdpoint) + while(na > 0 && a[na-1] == '0') + na--; + if(na == 0) + goto ret0; /* zero */ + a[na] = 0; + if(!(flag & Fdpoint)) + dp = na; + if(flag & Fesign) + ex = -ex; + dp += ex; + if(dp < -Maxe){ + errno = ERANGE; + goto ret0; /* underflow by exp */ + } else + if(dp > +Maxe) + goto retinf; /* overflow by exp */ + + /* + * normalize the decimal ascii number + * to range .[5-9][0-9]* e0 + */ + bp = 0; /* binary exponent */ + while(dp > 0) + divascii(a, &na, &dp, &bp); + while(dp < 0 || a[0] < '5') + mulascii(a, &na, &dp, &bp); + + /* close approx by naive conversion */ + mid[0] = 0; + mid[1] = 1; + for(i=0; (c=a[i]) != '\0'; i++) { + mid[0] = mid[0]*10 + (c-'0'); + mid[1] = mid[1]*10; + if(i >= 8) + break; + } + low[0] = umuldiv(mid[0], One, mid[1]); + hig[0] = umuldiv(mid[0]+1, One, mid[1]); + for(i=1; i>= 1; + } + frnorm(mid); + + /* compare */ + c = fpcmp(a, mid); + if(c > 0) { + c = 1; + for(i=0; i= Sigbit/2) { + mid[Prec-1] += Sigbit; + frnorm(mid); + } + goto out; + +ret0: + return 0; + +retnan: + return NAN; + +retinf: + /* + * Unix strtod requires these. Plan 9 would return Inf(0) or Inf(-1). */ + errno = ERANGE; + if(flag & Fsign) + return -HUGE_VAL; + return HUGE_VAL; + +out: + d = 0; + for(i=0; i0; i--) { + f[i] += c; + c = f[i] >> Nbits; + f[i] &= One-1; + } + f[0] += c; +} + +static int +fpcmp(char *a, ulong* f) +{ + ulong tf[Prec]; + int i, d, c; + + for(i=0; i> Nbits) + '0'; + tf[0] &= One-1; + + /* compare next digit */ + c = *a; + if(c == 0) { + if('0' < d) + return -1; + if(tf[0] != 0) + goto cont; + for(i=1; i d) + return +1; + if(c < d) + return -1; + a++; + cont:; + } +} + +static void +divby(char *a, int *na, int b) +{ + int n, c; + char *p; + + p = a; + n = 0; + while(n>>b == 0) { + c = *a++; + if(c == 0) { + while(n) { + c = n*10; + if(c>>b) + break; + n = c; + } + goto xx; + } + n = n*10 + c-'0'; + (*na)--; + } + for(;;) { + c = n>>b; + n -= c<>b; + n -= c<= (int)(nelem(tab1))) + d = (int)(nelem(tab1))-1; + t = tab1 + d; + b = t->bp; + if(memcmp(a, t->cmp, t->siz) > 0) + d--; + *dp -= d; + *bp += b; + divby(a, na, b); +} + +static void +mulby(char *a, char *p, char *q, int b) +{ + int n, c; + + n = 0; + *p = 0; + for(;;) { + q--; + if(q < a) + break; + c = *q - '0'; + c = (c<= (int)(nelem(tab2))) + d = (int)(nelem(tab2))-1; + t = tab2 + d; + b = t->bp; + if(memcmp(a, t->cmp, t->siz) < 0) + d--; + p = a + *na; + *bp -= b; + *dp += d; + *na += d; + mulby(a, p+d, p, b); +} + +static int +xcmp(char *a, char *b) +{ + int c1, c2; + + while((c1 = *b++) != '\0') { + c2 = *a++; + if(c2 >= 'A' && c2 <= 'Z') + c2 = c2 - 'A' + 'a'; + if(c1 != c2) + return 1; + } + return 0; +} diff --git a/jsi.h b/jsi.h index 0d53c03..529c7c2 100644 --- a/jsi.h +++ b/jsi.h @@ -58,6 +58,12 @@ const char *js_intern(js_State *J, const char *s); void jsS_dumpstrings(js_State *J); void jsS_freestrings(js_State *J); +/* Portable strtod and printf float formatting */ + +void js_fmtexp(char *p, int e); +void js_dtoa(double f, char *digits, int *exp, int *neg, int *ndigits); +double js_strtod(const char *as, char **aas); + /* Private stack functions */ void js_newfunction(js_State *J, js_Function *function, js_Environment *scope); diff --git a/jslex.c b/jslex.c index a3564da..284813d 100644 --- a/jslex.c +++ b/jslex.c @@ -310,7 +310,7 @@ static int lexnumber(js_State *J) if (jsY_isidentifierstart(PEEK)) jsY_error(J, "number with letter suffix"); - J->number = strtod(s, NULL); + J->number = js_strtod(s, NULL); return TK_NUMBER; } diff --git a/jsvalue.c b/jsvalue.c index 7b9d3c3..867e118 100644 --- a/jsvalue.c +++ b/jsvalue.c @@ -122,7 +122,7 @@ int jsV_toboolean(js_State *J, const js_Value *v) } } -double js_strtod(const char *s, char **ep) +double js_stringtofloat(const char *s, char **ep) { char *end; double n; @@ -136,7 +136,7 @@ double js_strtod(const char *s, char **ep) if (*e == '+' || *e == '-') ++e; while (*e >= '0' && *e <= '9') ++e; } - n = strtod(s, &end); + n = js_strtod(s, &end); if (end == e) { *ep = (char*)e; return n; @@ -160,7 +160,7 @@ double jsV_stringtonumber(js_State *J, const char *s) else if (!strncmp(s, "-Infinity", 9)) n = -INFINITY, e = (char*)s + 9; else - n = js_strtod(s, &e); + n = js_stringtofloat(s, &e); while (jsY_iswhite(*e) || jsY_isnewline(*e)) ++e; if (*e) return NAN; return n; @@ -199,7 +199,7 @@ const char *jsV_numbertostring(js_State *J, double f) if (isinf(f)) return f < 0 ? "-Infinity" : "Infinity"; if (f == 0) return "0"; - jsV_dtoa(f, digits, &exp, &neg, &ndigits); + js_dtoa(f, digits, &exp, &neg, &ndigits); point = ndigits + exp; if (neg) @@ -213,7 +213,7 @@ const char *jsV_numbertostring(js_State *J, double f) while (n--) *p++ = *s++; } - jsV_fmtexp(p, point - 1); + js_fmtexp(p, point - 1); } else if (point <= 0) { diff --git a/jsvalue.h b/jsvalue.h index 3add4fe..494691e 100644 --- a/jsvalue.h +++ b/jsvalue.h @@ -130,7 +130,7 @@ const char *jsV_tostring(js_State *J, const js_Value *v); js_Object *jsV_toobject(js_State *J, const js_Value *v); js_Value jsV_toprimitive(js_State *J, const js_Value *v, int preferred); -double js_strtod(const char *s, char **ep); +double js_stringtofloat(const char *s, char **ep); double jsV_numbertointeger(double n); int jsV_numbertoint32(double n); unsigned int jsV_numbertouint32(double n); @@ -139,10 +139,6 @@ unsigned short jsV_numbertouint16(double n); const char *jsV_numbertostring(js_State *J, double number); double jsV_stringtonumber(js_State *J, const char *string); -/* jsdtoa.c */ -void jsV_fmtexp(char *p, int e); -void jsV_dtoa(double f, char *digits, int *exp, int *neg, int *ndigits); - /* jsproperty.c */ js_Object *jsV_newobject(js_State *J, enum js_Class type, js_Object *prototype); js_Property *jsV_getownproperty(js_State *J, js_Object *obj, const char *name);