From 7dd286885631509390214d26d4bafa716c934c0b Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Fri, 1 Mar 2024 17:49:46 +0100 Subject: [PATCH] Improve Number.prototype.toString for radix other than 10 (#284) - fix the conversions for integers and exact fractions - approximate approach for other cases. - bypass floating point conversions for JS_TAG_INT values - avoid divisions for base 10 integer conversions Fixes: https://github.com/quickjs-ng/quickjs/issues/242 --- quickjs.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++----- v8.txt | 104 ---------------------------------------------------- 2 files changed, 98 insertions(+), 113 deletions(-) diff --git a/quickjs.c b/quickjs.c index 7700b63..bd80e3c 100644 --- a/quickjs.c +++ b/quickjs.c @@ -10971,6 +10971,8 @@ static JSValue js_bigint_to_string(JSContext *ctx, JSValue val) } /* 2 <= base <= 36 */ +static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + static char *i64toa(char *buf_end, int64_t n, unsigned int base) { char *q = buf_end; @@ -10982,15 +10984,20 @@ static char *i64toa(char *buf_end, int64_t n, unsigned int base) n = -n; } *--q = '\0'; - do { - digit = (uint64_t)n % base; - n = (uint64_t)n / base; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - *--q = digit; - } while (n != 0); + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digits[digit]; + } while (n != 0); + } if (is_neg) *--q = '-'; return q; @@ -11238,6 +11245,80 @@ static JSValue js_dtoa(JSContext *ctx, return JS_NewString(ctx, buf); } +static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix) +{ + char buf[2200], *ptr, *ptr2; + /* d is finite */ + int sign = d < 0; + int digit; + double frac, d0; + int64_t n0 = 0; + d = fabs(d); + d0 = trunc(d); + frac = d - d0; + ptr = buf + 1100; + *ptr = '\0'; + if (d0 <= MAX_SAFE_INTEGER) { + int64_t n = n0 = (int64_t)d0; + while (n >= radix) { + digit = n % radix; + n = n / radix; + *--ptr = digits[digit]; + } + *--ptr = digits[(int)n]; + } else { + /* no decimals */ + while (d0 >= radix) { + digit = fmod(d0, radix); + d0 = trunc(d0 / radix); + if (d0 >= MAX_SAFE_INTEGER) + digit = 0; + *--ptr = digits[digit]; + } + *--ptr = digits[(int)d0]; + goto done; + } + if (frac != 0) { + double log2_radix = log2(radix); + double prec = 1023 + 51; // handle subnormals + ptr2 = buf + 1100; + *ptr2++ = '.'; + while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) { + frac *= radix; + digit = trunc(frac); + frac -= digit; + *ptr2++ = digits[digit]; + n0 = n0 * radix + digit; + prec -= log2_radix; + } + *ptr2 = '\0'; + if (frac * radix >= radix / 2) { + char nine = digits[radix - 1]; + // round to closest + while (ptr2[-1] == nine) + *--ptr2 = '\0'; + if (ptr2[-1] == '.') { + *--ptr2 = '\0'; + while (ptr2[-1] == nine) + *--ptr2 = '0'; + } + if (ptr2 - 1 == ptr) + *--ptr = '1'; + else + ptr2[-1] += 1; + } else { + while (ptr2[-1] == '0') + *--ptr2 = '\0'; + if (ptr2[-1] == '.') + *--ptr2 = '\0'; + } + } +done: + ptr[-1] = '-'; + ptr -= sign; + return JS_NewString(ctx, ptr); +} + JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, BOOL is_ToPropertyKey) { uint32_t tag; @@ -38557,8 +38638,16 @@ static JSValue js_number_toString(JSContext *ctx, JSValue this_val, if (base < 0) goto fail; } + if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { + char buf1[70], *ptr; + ptr = i64toa(buf1 + sizeof(buf1), JS_VALUE_GET_INT(val), base); + return JS_NewString(ctx, ptr); + } if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; + if (base != 10 && isfinite(d)) { + return js_dtoa_radix(ctx, d, base); + } return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT); fail: JS_FreeValue(ctx, val); diff --git a/v8.txt b/v8.txt index 79e0543..ff7a225 100644 --- a/v8.txt +++ b/v8.txt @@ -799,113 +799,9 @@ Failure: expected found === number-string-index-call.js === number-tostring-add.js === number-tostring-big-integer.js -Failure: -expected: -"314404114120101444444424000000000000000" -found: -"1.2345e+27" === number-tostring-func.js -Failure: expected <"5a.1eb851eb852"> found <"90.12"> -Failure: expected <"0.1999999999999a"> found <"0.1"> -Failure: expected <"0.028f5c28f5c28f6"> found <"0.01"> -Failure: expected <"0.032617c1bda511a"> found <"0.0123"> -Failure: expected <"605f9f6dd18bc8000"> found <"111111111111111110000"> -Failure: expected <"3c3bc3a4a2f75c0000"> found <"1.1111111111111111e+21"> -Failure: expected <"25a55a46e5da9a00000"> found <"1.1111111111111111e+22"> -Failure: expected <"0.0000a7c5ac471b4788"> found <"0.00001"> -Failure: expected <"0.000010c6f7a0b5ed8d"> found <"0.000001"> -Failure: expected <"0.000001ad7f29abcaf48"> found <"1e-7"> -Failure: expected <"0.000002036565348d256"> found <"1.2e-7"> -Failure: expected <"0.0000021047ee22aa466"> found <"1.23e-7"> -Failure: expected <"0.0000002af31dc4611874"> found <"1e-8"> -Failure: expected <"0.000000338a23b87483be"> found <"1.2e-8"> -Failure: expected <"0.00000034d3fe36aaa0a2"> found <"1.23e-8"> -Failure: expected <"-5a.1eb851eb852"> found <"-90.12"> -Failure: expected <"-0.1999999999999a"> found <"-0.1"> -Failure: expected <"-0.028f5c28f5c28f6"> found <"-0.01"> -Failure: expected <"-0.032617c1bda511a"> found <"-0.0123"> -Failure: expected <"-605f9f6dd18bc8000"> found <"-111111111111111110000"> -Failure: expected <"-3c3bc3a4a2f75c0000"> found <"-1.1111111111111111e+21"> -Failure: expected <"-25a55a46e5da9a00000"> found <"-1.1111111111111111e+22"> -Failure: expected <"-0.0000a7c5ac471b4788"> found <"-0.00001"> -Failure: expected <"-0.000010c6f7a0b5ed8d"> found <"-0.000001"> -Failure: expected <"-0.000001ad7f29abcaf48"> found <"-1e-7"> -Failure: expected <"-0.000002036565348d256"> found <"-1.2e-7"> -Failure: expected <"-0.0000021047ee22aa466"> found <"-1.23e-7"> -Failure: expected <"-0.0000002af31dc4611874"> found <"-1e-8"> -Failure: expected <"-0.000000338a23b87483be"> found <"-1.2e-8"> -Failure: expected <"-0.00000034d3fe36aaa0a2"> found <"-1.23e-8"> -Failure: expected <"100000000000080"> found <"72057594037928060"> -Failure: expected <"1000000000000100"> found <"1152921504606847200"> -Failure: expected <"1000000000000000"> found <"1152921504606847000"> -Failure: expected <"1000000000000000"> found <"1152921504606847000"> -Failure: -expected: -"100000000000000000000000000000000000000000000000010000000" -found: -"72057594037928060" -Failure: expected <"-100000000000080"> found <"-72057594037928060"> -Failure: -expected: -"-100000000000000000000000000000000000000000000000010000000" -found: -"-72057594037928060" -Failure: expected <"8.8"> found <"8.5"> -Failure: expected <"-8.8"> found <"-8.5"> === number-tostring-small.js === number-tostring.js -Failure: expected <"5a.1eb851eb852"> found <"90.12"> -Failure: expected <"0.1999999999999a"> found <"0.1"> -Failure: expected <"0.028f5c28f5c28f6"> found <"0.01"> -Failure: expected <"0.032617c1bda511a"> found <"0.0123"> -Failure: expected <"605f9f6dd18bc8000"> found <"111111111111111110000"> -Failure: expected <"3c3bc3a4a2f75c0000"> found <"1.1111111111111111e+21"> -Failure: expected <"25a55a46e5da9a00000"> found <"1.1111111111111111e+22"> -Failure: expected <"0.0000a7c5ac471b4788"> found <"0.00001"> -Failure: expected <"0.000010c6f7a0b5ed8d"> found <"0.000001"> -Failure: expected <"0.000001ad7f29abcaf48"> found <"1e-7"> -Failure: expected <"0.000002036565348d256"> found <"1.2e-7"> -Failure: expected <"0.0000021047ee22aa466"> found <"1.23e-7"> -Failure: expected <"0.0000002af31dc4611874"> found <"1e-8"> -Failure: expected <"0.000000338a23b87483be"> found <"1.2e-8"> -Failure: expected <"0.00000034d3fe36aaa0a2"> found <"1.23e-8"> -Failure: expected <"-5a.1eb851eb852"> found <"-90.12"> -Failure: expected <"-0.1999999999999a"> found <"-0.1"> -Failure: expected <"-0.028f5c28f5c28f6"> found <"-0.01"> -Failure: expected <"-0.032617c1bda511a"> found <"-0.0123"> -Failure: expected <"-605f9f6dd18bc8000"> found <"-111111111111111110000"> -Failure: expected <"-3c3bc3a4a2f75c0000"> found <"-1.1111111111111111e+21"> -Failure: expected <"-25a55a46e5da9a00000"> found <"-1.1111111111111111e+22"> -Failure: expected <"-0.0000a7c5ac471b4788"> found <"-0.00001"> -Failure: expected <"-0.000010c6f7a0b5ed8d"> found <"-0.000001"> -Failure: expected <"-0.000001ad7f29abcaf48"> found <"-1e-7"> -Failure: expected <"-0.000002036565348d256"> found <"-1.2e-7"> -Failure: expected <"-0.0000021047ee22aa466"> found <"-1.23e-7"> -Failure: expected <"-0.0000002af31dc4611874"> found <"-1e-8"> -Failure: expected <"-0.000000338a23b87483be"> found <"-1.2e-8"> -Failure: expected <"-0.00000034d3fe36aaa0a2"> found <"-1.23e-8"> -Failure: expected <"100000000000080"> found <"72057594037928060"> -Failure: expected <"1000000000000100"> found <"1152921504606847200"> -Failure: expected <"1000000000000000"> found <"1152921504606847000"> -Failure: expected <"1000000000000000"> found <"1152921504606847000"> -Failure: -expected: -"100000000000000000000000000000000000000000000000010000000" -found: -"72057594037928060" -Failure: expected <"-100000000000080"> found <"-72057594037928060"> -Failure: -expected: -"-100000000000000000000000000000000000000000000000010000000" -found: -"-72057594037928060" -Failure: expected <"8.8"> found <"8.5"> -Failure: expected <"-8.8"> found <"-8.5"> -Failure: expected <"1.1"> found <"1.3333333333333333"> -Failure: expected <"11.1"> found <"4.333333333333333"> -Failure: expected <"0.01"> found <"0.1111111111111111"> -Failure: expected <"10000.01"> found <"81.11111111111111"> -Failure: expected <"0.0212010212010212010212010212010212"> found <"0.2857142857142857"> === numops-fuzz-part1.js === numops-fuzz-part2.js === numops-fuzz-part3.js