summaryrefslogtreecommitdiff
path: root/quickjs.c
diff options
context:
space:
mode:
authorCharlie Gordon <github@chqrlie.org>2024-03-02 15:13:18 +0100
committerCharlie Gordon <github@chqrlie.org>2024-03-02 15:13:18 +0100
commit8d64731eb892ddf8ea81d4050e8d9075aa4ee954 (patch)
treeb083653d1a012eba727a9b0cbea59e791e50ef17 /quickjs.c
parenta78d2cbf7ca0f2769e25618868beeb2e6ca440e7 (diff)
downloadquickjs-8d64731eb892ddf8ea81d4050e8d9075aa4ee954.tar.gz
quickjs-8d64731eb892ddf8ea81d4050e8d9075aa4ee954.zip
Improve Number.prototype.toString for radix other than 10
- 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
Diffstat (limited to 'quickjs.c')
-rw-r--r--quickjs.c107
1 files changed, 98 insertions, 9 deletions
diff --git a/quickjs.c b/quickjs.c
index f8c07d2..e34270f 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -11328,6 +11328,8 @@ static JSValue js_bigdecimal_to_string(JSContext *ctx, JSValueConst val)
#endif /* CONFIG_BIGNUM */
/* 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;
@@ -11339,15 +11341,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;
@@ -11595,6 +11602,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, JSValueConst val, BOOL is_ToPropertyKey)
{
uint32_t tag;
@@ -40975,8 +41056,16 @@ static JSValue js_number_toString(JSContext *ctx, JSValueConst 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);