]> git.kaiwu.me - quickjs.git/commitdiff
added new dtoa library to print and parse float64 numbers. It is necessary to fix...
authorFabrice Bellard <fabrice@bellard.org>
Wed, 19 Mar 2025 18:07:57 +0000 (19:07 +0100)
committerFabrice Bellard <fabrice@bellard.org>
Wed, 19 Mar 2025 18:07:57 +0000 (19:07 +0100)
Makefile
dtoa.c [new file with mode: 0644]
dtoa.h [new file with mode: 0644]
quickjs.c
tests/test_builtin.js

index 8d134c862b9c2a1f3ad6321782df4211db074f82..8314cb7b461a9d8cecb25dfe18f45305c8a70ca9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -227,7 +227,7 @@ endif
 
 all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS)
 
-QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
+QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
 
 QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS)
 
diff --git a/dtoa.c b/dtoa.c
new file mode 100644 (file)
index 0000000..0f6be25
--- /dev/null
+++ b/dtoa.c
@@ -0,0 +1,1626 @@
+/*
+ * Tiny float64 printing and parsing library
+ *
+ * Copyright (c) 2024 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <math.h>
+#include <setjmp.h>
+
+#include "cutils.h"
+#include "dtoa.h"
+
+/* 
+   TODO:
+   - test n_digits=101 instead of 100
+   - simplify subnormal handling
+   - reduce max memory usage
+   - free format: could add shortcut if exact result
+   - use 64 bit limb_t when possible
+   - use another algorithm for free format dtoa in base 10 (ryu ?)
+*/
+
+#define USE_POW5_TABLE
+/* use fast path to print small integers in free format */
+#define USE_FAST_INT
+
+#define LIMB_LOG2_BITS 5
+
+#define LIMB_BITS (1 << LIMB_LOG2_BITS)
+
+typedef int32_t slimb_t;
+typedef uint32_t limb_t;
+typedef uint64_t dlimb_t;
+
+#define LIMB_DIGITS 9
+
+#define JS_RADIX_MAX 36
+
+#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */
+#define MANT_LEN_MAX 18 /* < 36^100 */
+
+typedef intptr_t mp_size_t;
+
+/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */
+typedef struct {
+    int len; /* >= 1 */
+    limb_t tab[];
+} mpb_t;
+
+static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n)
+{
+    size_t i;
+    limb_t k, a;
+
+    k=b;
+    for(i=0;i<n;i++) {
+        if (k == 0)
+            break;
+        a = tab[i] + k;
+        k = (a < k);
+        tab[i] = a;
+    }
+    return k;
+}
+
+/* tabr[] = taba[] * b + l. Return the high carry */
+static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, 
+                      limb_t b, limb_t l)
+{
+    limb_t i;
+    dlimb_t t;
+
+    for(i = 0; i < n; i++) {
+        t = (dlimb_t)taba[i] * (dlimb_t)b + l;
+        tabr[i] = t;
+        l = t >> LIMB_BITS;
+    }
+    return l;
+}
+
+/* WARNING: d must be >= 2^(LIMB_BITS-1) */
+static inline limb_t udiv1norm_init(limb_t d)
+{
+    limb_t a0, a1;
+    a1 = -d - 1;
+    a0 = -1;
+    return (((dlimb_t)a1 << LIMB_BITS) | a0) / d;
+}
+
+/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0
+   / d' with 0 <= a1 < d. */
+static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0,
+                                limb_t d, limb_t d_inv)
+{
+    limb_t n1m, n_adj, q, r, ah;
+    dlimb_t a;
+    n1m = ((slimb_t)a0 >> (LIMB_BITS - 1));
+    n_adj = a0 + (n1m & d);
+    a = (dlimb_t)d_inv * (a1 - n1m) + n_adj;
+    q = (a >> LIMB_BITS) + a1;
+    /* compute a - q * r and update q so that the remainder is between
+       0 and d - 1 */
+    a = ((dlimb_t)a1 << LIMB_BITS) | a0;
+    a = a - (dlimb_t)q * d - d;
+    ah = a >> LIMB_BITS;
+    q += 1 + ah;
+    r = (limb_t)a + (ah & d);
+    *pr = r;
+    return q;
+}
+
+static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n,
+                      limb_t b, limb_t r)
+{
+    slimb_t i;
+    dlimb_t a1;
+    for(i = n - 1; i >= 0; i--) {
+        a1 = ((dlimb_t)r << LIMB_BITS) | taba[i];
+        tabr[i] = a1 / b;
+        r = a1 % b;
+    }
+    return r;
+}
+
+/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). 
+   1 <= shift <= LIMB_BITS - 1 */
+static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, 
+                     int shift, limb_t high)
+{
+    mp_size_t i;
+    limb_t l, a;
+
+    assert(shift >= 1 && shift < LIMB_BITS);
+    l = high;
+    for(i = n - 1; i >= 0; i--) {
+        a = tab[i];
+        tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift));
+        l = a;
+    }
+    return l & (((limb_t)1 << shift) - 1);
+}
+
+/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low <
+   2^shift. */
+static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, 
+              int shift, limb_t low)
+{
+    mp_size_t i;
+    limb_t l, a;
+
+    assert(shift >= 1 && shift < LIMB_BITS);
+    l = low;
+    for(i = 0; i < n; i++) {
+        a = tab[i];
+        tab_r[i] = (a << shift) | l;
+        l = (a >> (LIMB_BITS - shift)); 
+    }
+    return l;
+}
+
+static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n,
+                                    limb_t b, limb_t r, limb_t b_inv, int shift)
+{
+    slimb_t i;
+
+    if (shift != 0) {
+        r = (r << shift) | mp_shl(tabr, taba, n, shift, 0);
+    }
+    for(i = n - 1; i >= 0; i--) {
+        tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv);
+    }
+    r >>= shift;
+    return r;
+}
+
+static __maybe_unused void mpb_dump(const char *str, const mpb_t *a)
+{
+    int i;
+    
+    printf("%s= 0x", str);
+    for(i = a->len - 1; i >= 0; i--) {
+        printf("%08x", a->tab[i]);
+        if (i != 0)
+            printf("_");
+    }
+    printf("\n");
+}
+
+static void mpb_renorm(mpb_t *r)
+{
+    while (r->len > 1 && r->tab[r->len - 1] == 0)
+        r->len--;
+}
+
+#ifdef USE_POW5_TABLE
+static const uint32_t pow5_table[17] = {
+    0x00000005, 0x00000019, 0x0000007d, 0x00000271, 
+    0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, 
+    0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, 
+    0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, 
+    0xa2bc2ec5, 
+};
+
+static const uint8_t pow5h_table[4] = {
+    0x00000001, 0x00000007, 0x00000023, 0x000000b1, 
+};
+
+static const uint32_t pow5_inv_table[13] = {
+    0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1,
+    0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23,
+    0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812,
+    0xc25c2684,
+};
+#endif
+
+/* return a^b */
+static uint64_t pow_ui(uint32_t a, uint32_t b)
+{
+    int i, n_bits;
+    uint64_t r;
+    if (b == 0)
+        return 1;
+    if (b == 1)
+        return a;
+#ifdef USE_POW5_TABLE
+    if ((a == 5 || a == 10) && b <= 17) {
+        r = pow5_table[b - 1];
+        if (b >= 14) {
+            r |= (uint64_t)pow5h_table[b - 14] << 32;
+        }
+        if (a == 10)
+            r <<= b;
+        return r;
+    }
+#endif
+    r = a;
+    n_bits = 32 - clz32(b);
+    for(i = n_bits - 2; i >= 0; i--) {
+        r *= r;
+        if ((b >> i) & 1)
+            r *= a;
+    }
+    return r;
+}
+
+static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b)
+{
+    uint32_t r_inv, r;
+    int shift;
+#ifdef USE_POW5_TABLE
+    if (a == 5 && b >= 1 && b <= 13) {
+        r = pow5_table[b - 1];
+        shift = clz32(r);
+        r <<= shift;
+        r_inv = pow5_inv_table[b - 1];
+    } else
+#endif
+    {
+        r = pow_ui(a, b);
+        shift = clz32(r);
+        r <<= shift;
+        r_inv = udiv1norm_init(r);
+    }
+    *pshift = shift;
+    *pr_inv = r_inv;
+    return r;
+}
+
+enum {
+    JS_RNDN, /* round to nearest, ties to even */
+    JS_RNDNA, /* round to nearest, ties away from zero */
+    JS_RNDZ,
+};
+
+static int mpb_get_bit(const mpb_t *r, int k)
+{
+    int l;
+    
+    l = (unsigned)k / LIMB_BITS;
+    k = k & (LIMB_BITS - 1);
+    if (l >= r->len)
+        return 0;
+    else
+        return (r->tab[l] >> k) & 1;
+}
+
+/* compute round(r / 2^shift). 'shift' can be negative */
+static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode)
+{
+    int l, i;
+
+    if (shift == 0)
+        return;
+    if (shift < 0) {
+        shift = -shift;
+        l = (unsigned)shift / LIMB_BITS;
+        shift = shift & (LIMB_BITS - 1);
+        if (shift != 0) {
+            r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0);
+            r->len++;
+            mpb_renorm(r);
+        }
+        if (l > 0) {
+            for(i = r->len - 1; i >= 0; i--)
+                r->tab[i + l] = r->tab[i];
+            for(i = 0; i < l; i++)
+                r->tab[i] = 0;
+            r->len += l;
+        }
+    } else {
+        limb_t bit1, bit2;
+        int k, add_one;
+        
+        switch(rnd_mode) {
+        default:
+        case JS_RNDZ:
+            add_one = 0;
+            break;
+        case JS_RNDN:
+        case JS_RNDNA:
+            bit1 = mpb_get_bit(r, shift - 1);
+            if (bit1) {
+                if (rnd_mode == JS_RNDNA) {
+                    bit2 = 1;
+                } else {
+                    /* bit2 = oring of all the bits after bit1 */
+                    bit2 = 0;
+                    if (shift >= 2) {
+                        k = shift - 1;
+                        l = (unsigned)k / LIMB_BITS;
+                        k = k & (LIMB_BITS - 1);
+                        for(i = 0; i < min_int(l, r->len); i++)
+                            bit2 |= r->tab[i];
+                        if (l < r->len)
+                            bit2 |= r->tab[l] & (((limb_t)1 << k) - 1);
+                    }
+                }
+                if (bit2) {
+                    add_one = 1;
+                } else {
+                    /* round to even */
+                    add_one = mpb_get_bit(r, shift);
+                }
+            } else {
+                add_one = 0;
+            }
+            break;
+        }
+
+        l = (unsigned)shift / LIMB_BITS;
+        shift = shift & (LIMB_BITS - 1);
+        if (l >= r->len) {
+            r->len = 1;
+            r->tab[0] = add_one;
+        } else {
+            if (l > 0) {
+                r->len -= l;
+                for(i = 0; i < r->len; i++)
+                    r->tab[i] = r->tab[i + l];
+            }
+            if (shift != 0) {
+                mp_shr(r->tab, r->tab, r->len, shift, 0);
+                mpb_renorm(r);
+            }
+            if (add_one) {
+                limb_t a;
+                a = mp_add_ui(r->tab, 1, r->len);
+                if (a)
+                    r->tab[r->len++] = a;
+            }
+        }
+    }
+}
+
+/* return -1, 0 or 1 */
+static int mpb_cmp(const mpb_t *a, const mpb_t *b)
+{
+    mp_size_t i;
+    if (a->len < b->len)
+        return -1;
+    else if (a->len > b->len)
+        return 1;
+    for(i = a->len - 1; i >= 0; i--) {
+        if (a->tab[i] != b->tab[i]) {
+            if (a->tab[i] < b->tab[i])
+                return -1;
+            else
+                return 1;
+        }
+    }
+    return 0;
+}
+
+static void mpb_set_u64(mpb_t *r, uint64_t m)
+{
+#if LIMB_BITS == 64
+    r->tab[0] = m;
+    r->len = 1;
+#else
+    r->tab[0] = m;
+    r->tab[1] = m >> LIMB_BITS;
+    if (r->tab[1] == 0)
+        r->len = 1;
+    else
+        r->len = 2;
+#endif
+}
+
+static uint64_t mpb_get_u64(mpb_t *r)
+{
+#if LIMB_BITS == 64
+    return r->tab[0];
+#else
+    if (r->len == 1) {
+        return r->tab[0];
+    } else {
+        return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS);
+    }
+#endif
+}
+
+/* floor_log2() = position of the first non zero bit or -1 if zero. */
+static int mpb_floor_log2(mpb_t *a)
+{
+    limb_t v;
+    v = a->tab[a->len - 1];
+    if (v == 0)
+        return -1;
+    else
+        return a->len * LIMB_BITS - 1 - clz32(v);
+}
+
+#define MUL_LOG2_RADIX_BASE_LOG2 24
+
+/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */
+static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = {
+    0x000000, 0xa1849d, 0x000000, 0x6e40d2, 
+    0x6308c9, 0x5b3065, 0x000000, 0x50c24e, 
+    0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, 
+    0x433d00, 0x418677, 0x000000, 0x3ea16b, 
+    0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, 
+    0x39680b, 0x3897b3, 0x37d5af, 0x372069, 
+    0x367686, 0x35d6df, 0x354072, 0x34b261, 
+    0x342bea, 0x33ac62, 0x000000, 0x32bfd9, 
+    0x3251dd, 0x31e8d6, 0x318465,
+};
+
+/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */
+static int mul_log2_radix(int a, int radix)
+{
+    int radix_bits, mult;
+
+    if ((radix & (radix - 1)) == 0) {
+        /* if the radix is a power of two better to do it exactly */
+        radix_bits = 31 - clz32(radix);
+        if (a < 0)
+            a -= radix_bits - 1;
+        return a / radix_bits;
+    } else {
+        mult = mul_log2_radix_table[radix - 2];
+        return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2;
+    }
+}
+
+#if 0
+static void build_mul_log2_radix_table(void)
+{
+    int base, radix, mult, col, base_log2;
+
+    base_log2 = 24;
+    base = 1 << base_log2;
+    col = 0;
+    for(radix = 2; radix <= 36; radix++) {
+        if ((radix & (radix - 1)) == 0)
+            mult = 0;
+        else
+            mult = lrint((double)base / log2(radix));
+        printf("0x%06x, ", mult);
+        if (++col == 4) {
+            printf("\n");
+            col = 0;
+        }
+    }
+    printf("\n");
+}
+
+static void mul_log2_radix_test(void)
+{
+    int radix, i, ref, r;
+    
+    for(radix = 2; radix <= 36; radix++) {
+        for(i = -2048; i <= 2047; i++) {
+            ref = (int)floor((double)i / log2(radix));
+            r = mul_log2_radix(i, radix);
+            if (ref != r) {
+                printf("ERROR: radix=%d i=%d r=%d ref=%d\n",
+                       radix, i, r, ref);
+                exit(1);
+            }
+        }
+    }
+    if (0)
+        build_mul_log2_radix_table();
+}
+#endif
+
+static void u32toa_len(char *buf, uint32_t n, size_t len)
+{
+    int digit, i;
+    for(i = len - 1; i >= 0; i--) {
+        digit = n % 10;
+        n = n / 10;
+        buf[i] = digit + '0';
+    }
+}
+
+/* for power of 2 radixes. len >= 1 */
+static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len)
+{
+    int digit, i;
+    unsigned int mask;
+
+    mask = (1 << radix_bits) - 1;
+    for(i = len - 1; i >= 0; i--) {
+        digit = n & mask;
+        n >>= radix_bits;
+        if (digit < 10)
+            digit += '0';
+        else
+            digit += 'a' - 10;
+        buf[i] = digit;
+    }
+}
+
+/* len >= 1. 2 <= radix <= 36 */
+static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len)
+{
+    int digit, i;
+
+    if (radix == 10) {
+        /* specific case with constant divisor */
+#if LIMB_BITS == 32
+        u32toa_len(buf, n, len);
+#else
+        /* XXX: optimize */
+        for(i = len - 1; i >= 0; i--) {
+            digit = (limb_t)n % 10;
+            n = (limb_t)n / 10;
+            buf[i] = digit + '0';
+        }
+#endif
+    } else {
+        for(i = len - 1; i >= 0; i--) {
+            digit = (limb_t)n % radix;
+            n = (limb_t)n / radix;
+            if (digit < 10)
+                digit += '0';
+            else
+                digit += 'a' - 10;
+            buf[i] = digit;
+        }
+    }
+}
+
+size_t u32toa(char *buf, uint32_t n)
+{
+    char buf1[10], *q;
+    size_t len;
+    
+    q = buf1 + sizeof(buf1);
+    do {
+        *--q = n % 10 + '0';
+        n /= 10;
+    } while (n != 0);
+    len = buf1 + sizeof(buf1) - q;
+    memcpy(buf, q, len);
+    return len;
+}
+
+size_t i32toa(char *buf, int32_t n)
+{
+    if (n >= 0) {
+        return u32toa(buf, n);
+    } else {
+        buf[0] = '-';
+        return u32toa(buf + 1, -(uint32_t)n) + 1;
+    }
+}
+
+#ifdef USE_FAST_INT
+size_t u64toa(char *buf, uint64_t n)
+{
+    if (n < 0x100000000) {
+        return u32toa(buf, n);
+    } else {
+        uint64_t n1;
+        char *q = buf;
+        uint32_t n2;
+        
+        n1 = n / 1000000000;
+        n %= 1000000000;
+        if (n1 >= 0x100000000) {
+            n2 = n1 / 1000000000;
+            n1 = n1 % 1000000000;
+            /* at most two digits */
+            if (n2 >= 10) {
+                *q++ = n2 / 10 + '0';
+                n2 %= 10;
+            }
+            *q++ = n2 + '0';
+            u32toa_len(q, n1, 9);
+            q += 9;
+        } else {
+            q += u32toa(q, n1);
+        }
+        u32toa_len(q, n, 9);
+        q += 9;
+        return q - buf;
+    }
+}
+
+size_t i64toa(char *buf, int64_t n)
+{
+    if (n >= 0) {
+        return u64toa(buf, n);
+    } else {
+        buf[0] = '-';
+        return u64toa(buf + 1, -(uint64_t)n) + 1;
+    }
+}
+
+/* XXX: only tested for 1 <= n < 2^53 */
+size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix)
+{
+    int radix_bits, l;
+    if (likely(radix == 10))
+        return u64toa(buf, n);
+    if ((radix & (radix - 1)) == 0) {
+        radix_bits = 31 - clz32(radix);
+        if (n == 0)
+            l = 1;
+        else
+            l = (64 - clz64(n) + radix_bits - 1) / radix_bits;
+        u64toa_bin_len(buf, n, radix_bits, l);
+        return l;
+    } else {
+        char buf1[41], *q; /* maximum length for radix = 3 */
+        size_t len;
+        int digit;
+        q = buf1 + sizeof(buf1);
+        do {
+            digit = n % radix;
+            n /= radix;
+            if (digit < 10)
+                digit += '0';
+            else
+                digit += 'a' - 10;
+            *--q = digit;
+        } while (n != 0);
+        len = buf1 + sizeof(buf1) - q;
+        memcpy(buf, q, len);
+        return len;
+    }
+}
+
+size_t i64toa_radix(char *buf, int64_t n, unsigned int radix)
+{
+    if (n >= 0) {
+        return u64toa_radix(buf, n, radix);
+    } else {
+        buf[0] = '-';
+        return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1;
+    }
+}
+#endif /* USE_FAST_INT */
+
+static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = {
+#if LIMB_BITS == 32
+32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+#else
+64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12,
+#endif
+};
+
+static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = {
+ 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395,
+ 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91,
+ 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021,
+ 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571,
+ 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d,
+ 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51,
+ 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899,
+ 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1,
+ 0x5c13d840, 0x6d91b519, 0x81bf1000,
+};
+
+/* XXX: remove the table ? */
+static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = {
+    54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12,
+};
+
+/* we limit the maximum number of significant digits for atod to about
+   128 bits of precision for non power of two bases. The only
+   requirement for Javascript is at least 20 digits in base 10. For
+   power of two bases, we do an exact rounding in all the cases. */
+static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = {
+     64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24,
+};
+
+/* if abs(d) >= B^max_exponent, it is an overflow */
+static const int16_t max_exponent[JS_RADIX_MAX - 1] = {
+ 1024,   647,   512,   442,   397,   365,   342,   324, 
+  309,   297,   286,   277,   269,   263,   256,   251, 
+  246,   242,   237,   234,   230,   227,   224,   221, 
+  218,   216,   214,   211,   209,   207,   205,   203, 
+  202,   200,   199, 
+};
+
+/* if abs(d) <= B^min_exponent, it is an underflow */
+static const int16_t min_exponent[JS_RADIX_MAX - 1] = {
+-1075,  -679,  -538,  -463,  -416,  -383,  -359,  -340, 
+ -324,  -311,  -300,  -291,  -283,  -276,  -269,  -263, 
+ -258,  -254,  -249,  -245,  -242,  -238,  -235,  -232, 
+ -229,  -227,  -224,  -222,  -220,  -217,  -215,  -214, 
+ -212,  -210,  -208, 
+};
+
+#if 0
+void build_tables(void)
+{
+    int r, j, radix, n, col, i;
+    
+    /* radix_base_table */
+    for(radix = 2; radix <= 36; radix++) {
+        r = 1;
+        for(j = 0; j < digits_per_limb_table[radix - 2]; j++) {
+            r *= radix;
+        }
+        printf(" 0x%08x,", r);
+        if ((radix % 4) == 1)
+            printf("\n");
+    }
+    printf("\n");
+
+    /* dtoa_max_digits_table */
+    for(radix = 2; radix <= 36; radix++) {
+        /* Note: over estimated when the radix is a power of two */
+        printf(" %d,", 1 + (int)ceil(53.0 / log2(radix)));
+    }
+    printf("\n");
+
+    /* atod_max_digits_table */
+    for(radix = 2; radix <= 36; radix++) {
+        if ((radix & (radix - 1)) == 0) {
+            /* 64 bits is more than enough */
+            n = (int)floor(64.0 / log2(radix));
+        } else {
+            n = (int)floor(128.0 / log2(radix));
+        }
+        printf(" %d,", n);
+    }
+    printf("\n");
+
+    printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n");
+    col = 0;
+    for(radix = 2; radix <= 36; radix++) {
+        printf("%5d, ", (int)ceil(1024 / log2(radix)));
+        if (++col == 8) {
+            col = 0;
+            printf("\n");
+        }
+    }
+    printf("\n};\n\n");
+
+    printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n");
+    col = 0; 
+    for(radix = 2; radix <= 36; radix++) {
+        printf("%5d, ", (int)floor(-1075 / log2(radix)));
+        if (++col == 8) {
+            col = 0;
+            printf("\n");
+        }
+    }
+    printf("\n};\n\n");
+
+    printf("static const uint32_t pow5_table[16] = {\n");
+    col = 0; 
+    for(i = 2; i <= 17; i++) {
+        r = 1;
+        for(j = 0; j < i; j++) {
+            r *= 5;
+        }
+        printf("0x%08x, ", r);
+        if (++col == 4) {
+            col = 0;
+            printf("\n");
+        }
+    }
+    printf("\n};\n\n");
+
+    /* high part */
+    printf("static const uint8_t pow5h_table[4] = {\n");
+    col = 0; 
+    for(i = 14; i <= 17; i++) {
+        uint64_t r1;
+        r1 = 1;
+        for(j = 0; j < i; j++) {
+            r1 *= 5;
+        }
+        printf("0x%08x, ", (uint32_t)(r1 >> 32));
+        if (++col == 4) {
+            col = 0;
+            printf("\n");
+        }
+    }
+    printf("\n};\n\n");
+}
+#endif
+
+/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits,
+   the dot is not displayed. 'a' is modified. */
+static int output_digits(char *buf,
+                         mpb_t *a, int radix, int n_digits1,
+                         int dot_pos)
+{
+    int n_digits, digits_per_limb, radix_bits, n, len;
+
+    n_digits = n_digits1;
+    if ((radix & (radix - 1)) == 0) {
+        /* radix = 2^radix_bits */
+        radix_bits = 31 - clz32(radix);
+    } else {
+        radix_bits = 0;
+    }
+    digits_per_limb = digits_per_limb_table[radix - 2];
+    if (radix_bits != 0) {
+        for(;;) {
+            n = min_int(n_digits, digits_per_limb);
+            n_digits -= n;
+            u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n);
+            if (n_digits == 0)
+                break;
+            mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ);
+        }
+    } else {
+        limb_t r;
+        while (n_digits != 0) {
+            n = min_int(n_digits, digits_per_limb);
+            n_digits -= n;
+            r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0);
+            mpb_renorm(a);
+            limb_to_a(buf + n_digits, r, radix, n);
+        }
+    }
+
+    /* add the dot */
+    len = n_digits1;
+    if (dot_pos != n_digits1) {
+        memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos);
+        buf[dot_pos] = '.';
+        len++;
+    }
+    return len;
+}
+
+/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f *
+   2^-e_offset. 'f' can be negative. */
+static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, BOOL is_int, int e)
+{
+    int e_offset, d, n, n0;
+
+    e_offset = -f * radix_shift;
+    if (radix1 != 1) {
+        d = digits_per_limb_table[radix1 - 2];
+        if (f >= 0) {
+            limb_t h, b;
+            
+            b = 0;
+            n0 = 0;
+            while (f != 0) {
+                n = min_int(f, d);
+                if (n != n0) {
+                    b = pow_ui(radix1, n);
+                    n0 = n;
+                }
+                h = mp_mul1(a->tab, a->tab, a->len, b, 0);
+                if (h != 0) {
+                    a->tab[a->len++] = h;
+                }
+                f -= n;
+            }
+        } else {
+            int extra_bits, l, shift;
+            limb_t r, rem, b, b_inv;
+            
+            f = -f;
+            l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */
+            e_offset += l * LIMB_BITS;
+            if (!is_int) {
+                /* at least 'e' bits are needed in the final result for rounding */
+                extra_bits = max_int(e - mpb_floor_log2(a), 0);
+            } else {
+                /* at least two extra bits are needed in the final result
+                   for rounding */
+                extra_bits = max_int(2 + e - e_offset, 0);
+            }
+            e_offset += extra_bits;
+            mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ);
+            
+            b = 0;
+            b_inv = 0;
+            shift = 0;
+            n0 = 0;
+            rem = 0;
+            while (f != 0) {
+                n = min_int(f, d);
+                if (n != n0) {
+                    b = pow_ui_inv(&b_inv, &shift, radix1, n);
+                    n0 = n;
+                }
+                r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift);
+                rem |= r;
+                mpb_renorm(a);
+                f -= n;
+            }
+            /* if the remainder is non zero, use it for rounding */
+            a->tab[0] |= (rem != 0);
+        }
+    }
+    return e_offset;
+}
+
+/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */
+static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f,
+                          int rnd_mode)
+{
+    int e_offset;
+
+    mpb_set_u64(tmp1, m);
+    e_offset = mul_pow(tmp1, radix1, radix_shift, f, TRUE, e);
+    mpb_shr_round(tmp1, -e + e_offset, rnd_mode);
+}
+
+/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */
+static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode)
+{
+    int e;
+    uint64_t m;
+
+    if (a->tab[0] == 0 && a->len == 1) {
+        /* zero result */
+        m = 0;
+        e = 0; /* don't care */
+    } else {
+        int prec, prec1, e_min;
+        e = mpb_floor_log2(a) + 1 - e_offset;
+        prec1 = 53;
+        e_min = -1021;
+        if (e < e_min) {
+            /* subnormal result or zero */
+            prec = prec1 - (e_min - e);
+        } else {
+            prec = prec1;
+        }
+        mpb_shr_round(a, e + e_offset - prec, rnd_mode);
+        m = mpb_get_u64(a);
+        m <<= (53 - prec);
+        /* mantissa overflow due to rounding */
+        if (m >= (uint64_t)1 << 53) {
+            m >>= 1;
+            e++;
+        }
+    }
+    *pe = e;
+    return m;
+}
+
+/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52
+   <= m < 2^53 or m = 0.
+   'a' is modified. */
+static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a,
+                                   int radix1, int radix_shift, int f, int rnd_mode)
+{
+    int e_offset;
+
+    e_offset = mul_pow(a, radix1, radix_shift, f, FALSE, 55);
+    return round_to_d(pe, a, e_offset, rnd_mode);
+}
+
+#ifdef JS_DTOA_DUMP_STATS
+static int out_len_count[17];
+
+void js_dtoa_dump_stats(void)
+{
+    int i, sum;
+    sum = 0;
+    for(i = 0; i < 17; i++)
+        sum += out_len_count[i];
+    for(i = 0; i < 17; i++) {
+        printf("%2d %8d %5.2f%%\n",
+               i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100);
+    }
+}
+#endif
+
+/* return a maximum bound of the string length. The bound depends on
+   'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED
+   is enabled. */
+int js_dtoa_max_len(double d, int radix, int n_digits, int flags)
+{
+    int fmt = flags & JS_DTOA_FORMAT_MASK;
+    int n, e;
+    uint64_t a;
+
+    if (fmt != JS_DTOA_FORMAT_FRAC) {
+        if (fmt == JS_DTOA_FORMAT_FREE) {
+            n = dtoa_max_digits_table[radix - 2];
+        } else {
+            n = n_digits;
+        }
+        if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) {
+            /* no exponential */
+            a = float64_as_uint64(d);
+            e = (a >> 52) & 0x7ff;
+            if (e == 0x7ff) {
+                /* NaN, Infinity */
+                n = 0;
+            } else {
+                e -= 1023;
+                /* XXX: adjust */
+                n += 10 + abs(mul_log2_radix(e - 1, radix));
+            }
+        } else {
+            /* extra: sign, 1 dot and exponent "e-1000" */
+            n += 1 + 1 + 6;
+        }
+    } else {
+        a = float64_as_uint64(d);
+        e = (a >> 52) & 0x7ff;
+        if (e == 0x7ff) {
+            /* NaN, Infinity */
+            n = 0;
+        } else {
+            /* high bound for the integer part */
+            e -= 1023;
+            /* x < 2^(e + 1) */
+            if (e < 0) {
+                n = 1;
+            } else {
+                n = 2 + mul_log2_radix(e - 1, radix);
+            }
+            /* sign, extra digit, 1 dot */
+            n += 1 + 1 + 1 + n_digits;
+        }
+    }
+    return max_int(n, 9); /* also include NaN and [-]Infinity */
+}
+
+#if defined(__SANITIZE_ADDRESS__) && 0
+static void *dtoa_malloc(uint64_t **pptr, size_t size)
+{
+    return malloc(size);
+}
+static void dtoa_free(void *ptr)
+{
+    free(ptr);
+}
+#else
+static void *dtoa_malloc(uint64_t **pptr, size_t size)
+{
+    void *ret;
+    ret = *pptr;
+    *pptr += (size + 7) / 8;
+    return ret;
+}
+
+static void dtoa_free(void *ptr)
+{
+}
+#endif
+
+/* return the length */
+int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
+            JSDTOATempMem *tmp_mem)
+{
+    uint64_t a, m, *mptr = tmp_mem->mem;
+    int e, sgn, l, E, P, i, E_max, radix1, radix_shift;
+    char *q;
+    mpb_t *tmp1, *mant_max;
+    int fmt = flags & JS_DTOA_FORMAT_MASK;
+
+    tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX);
+    mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX);
+    assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0]));
+
+    radix_shift = ctz32(radix);
+    radix1 = radix >> radix_shift;
+    a = float64_as_uint64(d);
+    sgn = a >> 63;
+    e = (a >> 52) & 0x7ff;
+    m = a & (((uint64_t)1 << 52) - 1);
+    q = buf;
+    if (e == 0x7ff) {
+        if (m == 0) {
+            if (sgn)
+                *q++ = '-';
+            memcpy(q, "Infinity", 8);
+            q += 8;
+        } else {
+            memcpy(q, "NaN", 3);
+            q += 3;
+        }
+        goto done;
+    } else if (e == 0) {
+        if (m == 0) {
+            tmp1->len = 1;
+            tmp1->tab[0] = 0;
+            E = 1;
+            if (fmt == JS_DTOA_FORMAT_FREE)
+                P = 1;
+            else if (fmt == JS_DTOA_FORMAT_FRAC)
+                P = n_digits + 1;
+            else
+                P = n_digits;
+            goto output;
+        }
+        /* denormal number: convert to a normal number */
+        l = clz64(m) - 11;
+        e -= l - 1;
+        m <<= l;
+    } else {
+        m |= (uint64_t)1 << 52;
+    }
+    /* remove the bias */
+    e -= 1022;
+    /* d = 2^(e-53)*m */
+    //    printf("m=0x%016" PRIx64 " e=%d\n", m, e);
+#ifdef USE_FAST_INT
+    if (fmt == JS_DTOA_FORMAT_FREE &&
+        e >= 1 && e <= 53 &&
+        (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 &&
+        (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) {
+        m >>= 53 - e;
+        /* 'm' is never zero */
+        if (sgn)
+            *q++ = '-';
+        q += u64toa_radix(q, m, radix);
+        goto done;
+    }
+#endif
+    
+    /* this choice of E implies F=round(x*B^(P-E) is such as: 
+       B^(P-1) <= F < 2.B^P. */
+    E = 1 + mul_log2_radix(e - 1, radix);
+    
+    if (fmt == JS_DTOA_FORMAT_FREE) {
+        int P_max, E0, e1, E_found, P_found;
+        uint64_t m1, mant_found, mant, mant_max1;
+        /* P_max is guarranteed to work by construction */
+        P_max = dtoa_max_digits_table[radix - 2];
+        E0 = E;
+        E_found = 0;
+        P_found = 0;
+        mant_found = 0;
+        /* find the minimum number of digits by successive tries */
+        P = P_max; /* P_max is guarateed to work */
+        for(;;) {
+            /* mant_max always fits on 64 bits */
+            mant_max1 = pow_ui(radix, P);
+            /* compute the mantissa in base B */
+            E = E0;
+            for(;;) {
+                /* XXX: add inexact flag */
+                mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN);
+                mant = mpb_get_u64(tmp1);
+                if (mant < mant_max1)
+                    break;
+                E++; /* at most one iteration is possible */
+            }
+            /* remove useless trailing zero digits */
+            while ((mant % radix) == 0) {
+                mant /= radix;
+                P--;
+            }
+            /* garanteed to work for P = P_max */
+            if (P_found == 0)
+                goto prec_found;
+            /* convert back to base 2 */
+            mpb_set_u64(tmp1, mant);
+            m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN);
+            //            printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1);
+            /* Note: (m, e) is never zero here, so the exponent for m1
+               = 0 does not matter */
+            if (m1 == m && e1 == e) {
+            prec_found:
+                P_found = P;
+                E_found = E;
+                mant_found = mant;
+                if (P == 1)
+                    break;
+                P--; /* try lower exponent */
+            } else {
+                break;
+            }
+        }
+        P = P_found;
+        E = E_found;
+        mpb_set_u64(tmp1, mant_found);
+#ifdef JS_DTOA_DUMP_STATS
+        if (radix == 10) {
+            out_len_count[P - 1]++;
+        }
+#endif        
+    } else if (fmt == JS_DTOA_FORMAT_FRAC) {
+        int len;
+
+        assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS);
+        /* P = max_int(E, 1) + n_digits; */
+        /* frac is rounded using RNDNA */
+        mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA);
+
+        /* "-0" is displayed as "0" */
+        if (sgn && !(tmp1->tab[0] == 0 && tmp1->len == 1)) {
+            *q++ = '-';
+        }
+        /* we add one extra digit on the left and remove it if needed
+           to avoid testing if the result is < radix^P */
+        len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits,
+                            max_int(E + 1, 1));
+        if (q[0] == '0' && len >= 2 && q[1] != '.') {
+            len--;
+            memmove(q, q + 1, len);
+        }
+        q += len;
+        goto done;
+    } else {
+        int pow_shift;
+        assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS);
+        P = n_digits;
+        /* mant_max = radix^P */
+        mant_max->len = 1;
+        mant_max->tab[0] = 1;
+        pow_shift = mul_pow(mant_max, radix1, radix_shift, P, FALSE, 0);
+        mpb_shr_round(mant_max, pow_shift, JS_RNDZ);
+        
+        for(;;) {
+            /* fixed and frac are rounded using RNDNA */
+            mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA);
+            if (mpb_cmp(tmp1, mant_max) < 0)
+                break;
+            E++; /* at most one iteration is possible */
+        }
+    }
+ output:
+    /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */
+    if (sgn && ((flags & JS_DTOA_MINUS_ZERO) ||
+                !(tmp1->tab[0] == 0 && tmp1->len == 1))) {
+        *q++ = '-';
+    }
+    if (fmt == JS_DTOA_FORMAT_FIXED)
+        E_max = n_digits;
+    else
+        E_max = dtoa_max_digits_table[radix - 2] + 4;
+    if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED ||
+        ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) {
+        q += output_digits(q, tmp1, radix, P, 1);
+        E--;
+        if (radix == 10) {
+            *q++ = 'e';
+        } else if (radix1 == 1 && radix_shift <= 4) {
+            E *= radix_shift;
+            *q++ = 'p';
+        } else {
+            *q++ = '@';
+        }
+        if (E < 0) {
+            *q++ = '-';
+            E = -E;
+        } else {
+            *q++ = '+';
+        }
+        q += u32toa(q, E);
+    } else if (E <= 0) {
+        *q++ = '0';
+        *q++ = '.';
+        for(i = 0; i < -E; i++)
+            *q++ = '0';
+        q += output_digits(q, tmp1, radix, P, P);
+    } else {
+        q += output_digits(q, tmp1, radix, P, min_int(P, E));
+        for(i = 0; i < E - P; i++)
+            *q++ = '0';
+    }
+ done:
+    *q = '\0';
+    dtoa_free(mant_max);
+    dtoa_free(tmp1);
+    return q - buf;
+}
+
+static inline int to_digit(int c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    else if (c >= 'A' && c <= 'Z')
+        return c - 'A' + 10;
+    else if (c >= 'a' && c <= 'z')
+        return c - 'a' + 10;
+    else
+        return 36;
+}
+
+/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */
+static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a)
+{
+    int i;
+    if (r->tab[0] == 0 && r->len == 1) {
+        r->tab[0] = a;
+    } else {
+        if (radix_base == 0) {
+            for(i = r->len; i >= 0; i--) {
+                r->tab[i + 1] = r->tab[i];
+            }
+            r->tab[0] = a;
+        } else {
+            r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len,
+                                     radix_base, a);
+        }
+        r->len++;
+        mpb_renorm(r);
+    }
+}
+
+/* XXX: add fast path for small integers */
+double js_atod(const char *str, const char **pnext, int radix, int flags,
+               JSATODTempMem *tmp_mem)
+{
+    uint64_t *mptr = tmp_mem->mem;
+    const char *p, *p_start;
+    limb_t cur_limb, radix_base, extra_digits;
+    int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift;
+    int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos;
+    mpb_t *tmp0;
+    double dval;
+    BOOL is_bin_exp, is_zero, expn_overflow;
+    uint64_t m, a;
+
+    tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX);
+    assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0]));
+    /* optional separator between digits */
+    sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
+
+    p = str;
+    is_neg = 0;
+    if (p[0] == '+') {
+        p++;
+        p_start = p;
+    } else if (p[0] == '-') {
+        is_neg = 1;
+        p++;
+        p_start = p;
+    } else {
+        p_start = p;
+    }
+    
+    if (p[0] == '0') {
+        if ((p[1] == 'x' || p[1] == 'X') &&
+            (radix == 0 || radix == 16)) {
+            p += 2;
+            radix = 16;
+        } else if ((p[1] == 'o' || p[1] == 'O') &&
+                   radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) {
+            p += 2;
+            radix = 8;
+        } else if ((p[1] == 'b' || p[1] == 'B') &&
+                   radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) {
+            p += 2;
+            radix = 2;
+        } else if ((p[1] >= '0' && p[1] <= '9') &&
+                   radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) {
+            int i;
+            sep = 256;
+            for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++)
+                continue;
+            if (p[i] == '8' || p[i] == '9')
+                goto no_prefix;
+            p += 1;
+            radix = 8;
+        } else {
+            goto no_prefix;
+        }
+        /* there must be a digit after the prefix */
+        if (to_digit((uint8_t)*p) >= radix)
+            goto fail;
+    no_prefix: ;
+    } else {
+        if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p))
+            goto overflow;
+    }
+    if (radix == 0)
+        radix = 10;
+
+    cur_limb = 0;
+    expn_offset = 0;
+    digit_count = 0;
+    limb_digit_count = 0;
+    max_digits = atod_max_digits_table[radix - 2];
+    digits_per_limb = digits_per_limb_table[radix - 2];
+    radix_base = radix_base_table[radix - 2];
+    radix_shift = ctz32(radix);
+    radix1 = radix >> radix_shift;
+    if (radix1 == 1) {
+        /* radix = 2^radix_bits */
+        radix_bits = radix_shift;
+    } else {
+        radix_bits = 0;
+    }
+    tmp0->len = 1;
+    tmp0->tab[0] = 0;
+    extra_digits = 0;
+    pos = 0;
+    dot_pos = -1;
+    /* skip leading zeros */
+    for(;;) {
+        if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) &&
+            !(flags & JS_ATOD_INT_ONLY)) {
+            if (*p == sep)
+                goto fail;
+            if (dot_pos >= 0)
+                break;
+            dot_pos = pos;
+            p++;
+        }
+        if (*p == sep && p > p_start && p[1] == '0')
+            p++;
+        if (*p != '0')
+            break;
+        p++;
+        pos++;
+    }
+    
+    sig_pos = pos;
+    for(;;) {
+        limb_t c;
+        if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) &&
+            !(flags & JS_ATOD_INT_ONLY)) {
+            if (*p == sep)
+                goto fail;
+            if (dot_pos >= 0)
+                break;
+            dot_pos = pos;
+            p++;
+        }
+        if (*p == sep && p > p_start && to_digit(p[1]) < radix)
+            p++;
+        c = to_digit(*p);
+        if (c >= radix)
+            break;
+        p++;
+        pos++;
+        if (digit_count < max_digits) {
+            /* XXX: could be faster when radix_bits != 0 */
+            cur_limb = cur_limb * radix + c;
+            limb_digit_count++;
+            if (limb_digit_count == digits_per_limb) {
+                mpb_mul1_base(tmp0, radix_base, cur_limb);
+                cur_limb = 0;
+                limb_digit_count = 0;
+            }
+            digit_count++;
+        } else {
+            extra_digits |= c;
+        }
+    }
+    if (limb_digit_count != 0) {
+        mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb);
+    }
+    if (digit_count == 0) {
+        is_zero = TRUE;
+        expn_offset = 0;
+    } else {
+        is_zero = FALSE;
+        if (dot_pos < 0)
+            dot_pos = pos;
+        expn_offset = sig_pos + digit_count - dot_pos;
+    }
+    
+    /* Use the extra digits for rounding if the base is a power of
+       two. Otherwise they are just truncated. */
+    if (radix_bits != 0 && extra_digits != 0) {
+        tmp0->tab[0] |= 1;
+    }
+    
+    /* parse the exponent, if any */
+    expn = 0;
+    expn_overflow = FALSE;
+    is_bin_exp = FALSE;
+    if (!(flags & JS_ATOD_INT_ONLY) &&
+        ((radix == 10 && (*p == 'e' || *p == 'E')) ||
+         (radix != 10 && (*p == '@' ||
+                          (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) &&
+        p > p_start) {
+        BOOL exp_is_neg;
+        int c;
+        is_bin_exp = (*p == 'p' || *p == 'P');
+        p++;
+        exp_is_neg = 0;
+        if (*p == '+') {
+            p++;
+        } else if (*p == '-') {
+            exp_is_neg = 1;
+            p++;
+        }
+        c = to_digit(*p);
+        if (c >= 10)
+            goto fail; /* XXX: could stop before the exponent part */
+        expn = c;
+        p++;
+        for(;;) {
+            if (*p == sep && to_digit(p[1]) < 10)
+                p++;
+            c = to_digit(*p);
+            if (c >= 10)
+                break;
+            if (!expn_overflow) {
+                if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) {
+                    expn_overflow = TRUE;
+                } else {
+                    expn = expn * 10 + c;
+                }
+            }
+            p++;
+        }
+        if (exp_is_neg)
+            expn = -expn;
+        /* if zero result, the exponent can be arbitrarily large */
+        if (!is_zero && expn_overflow) {
+            if (exp_is_neg)
+                a = 0;
+            else
+                a = (uint64_t)0x7ff << 52; /* infinity */
+            goto done;
+        }
+    }
+
+    if (p == p_start)
+        goto fail;
+
+    if (is_zero) {
+        a = 0;
+    } else {
+        int expn1;
+        if (radix_bits != 0) {
+            if (!is_bin_exp)
+                expn *= radix_bits;
+            expn -= expn_offset * radix_bits;
+            expn1 = expn + digit_count * radix_bits;
+            if (expn1 >= 1024 + radix_bits)
+                goto overflow;
+            else if (expn1 <= -1075)
+                goto underflow;
+            m = round_to_d(&e, tmp0, -expn, JS_RNDN);
+        } else {
+            expn -= expn_offset;
+            expn1 = expn + digit_count;
+            if (expn1 >= max_exponent[radix - 2] + 1)
+                goto overflow;
+            else if (expn1 <= min_exponent[radix - 2])
+                goto underflow;
+            m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN);
+        }
+        if (m == 0) {
+        underflow:
+            a = 0;
+        } else if (e > 1024) {
+        overflow:
+            /* overflow */
+            a = (uint64_t)0x7ff << 52;
+        } else if (e < -1073) {
+            /* underflow */
+            /* XXX: check rounding */
+            a = 0;
+        } else if (e < -1021) {
+            /* subnormal */
+            a = m >> (-e - 1021);
+        } else {
+            a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1));
+        }
+    }
+ done:
+    a |= (uint64_t)is_neg << 63;
+    dval = uint64_as_float64(a);
+ done1:
+    if (pnext)
+        *pnext = p;
+    dtoa_free(tmp0);
+    return dval;
+ fail:
+    dval = NAN;
+    goto done1;
+}
diff --git a/dtoa.h b/dtoa.h
new file mode 100644 (file)
index 0000000..de76f1a
--- /dev/null
+++ b/dtoa.h
@@ -0,0 +1,83 @@
+/*
+ * Tiny float64 printing and parsing library
+ *
+ * Copyright (c) 2024 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//#define JS_DTOA_DUMP_STATS
+
+/* maximum number of digits for fixed and frac formats */
+#define JS_DTOA_MAX_DIGITS 101
+
+/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */
+/* use as many digits as necessary */
+#define JS_DTOA_FORMAT_FREE  (0 << 0)
+/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */
+#define JS_DTOA_FORMAT_FIXED (1 << 0)
+/* force fractional format: [-]dd.dd with n_digits fractional digits.
+   0 <= n_digits <= JS_DTOA_MAX_DIGITS */
+#define JS_DTOA_FORMAT_FRAC  (2 << 0)
+#define JS_DTOA_FORMAT_MASK  (3 << 0)
+
+/* select exponential notation either in fixed or free format */
+#define JS_DTOA_EXP_AUTO     (0 << 2)
+#define JS_DTOA_EXP_ENABLED  (1 << 2)
+#define JS_DTOA_EXP_DISABLED (2 << 2)
+#define JS_DTOA_EXP_MASK     (3 << 2)
+
+#define JS_DTOA_MINUS_ZERO   (1 << 4) /* show the minus sign for -0 */
+
+/* only accepts integers (no dot, no exponent) */
+#define JS_ATOD_INT_ONLY       (1 << 0)
+/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
+#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1)
+/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
+#define JS_ATOD_ACCEPT_LEGACY_OCTAL  (1 << 2)
+/* accept _ between digits as a digit separator */
+#define JS_ATOD_ACCEPT_UNDERSCORES  (1 << 3)
+
+typedef struct {
+    uint64_t mem[37];
+} JSDTOATempMem;
+
+typedef struct {
+    uint64_t mem[27];
+} JSATODTempMem;
+
+/* return a maximum bound of the string length */
+int js_dtoa_max_len(double d, int radix, int n_digits, int flags);
+/* return the string length */
+int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
+            JSDTOATempMem *tmp_mem);
+double js_atod(const char *str, const char **pnext, int radix, int flags,
+               JSATODTempMem *tmp_mem);
+
+#ifdef JS_DTOA_DUMP_STATS
+void js_dtoa_dump_stats(void);
+#endif
+
+/* additional exported functions */
+size_t u32toa(char *buf, uint32_t n);
+size_t i32toa(char *buf, int32_t n);
+size_t u64toa(char *buf, uint64_t n);
+size_t i64toa(char *buf, int64_t n);
+size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix);
+size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);
index f8c6d89364bbb59c91bf972939d414162d17d5d6..831f9618fa414a2930b981c39755a3ec40f81244 100644 (file)
--- a/quickjs.c
+++ b/quickjs.c
@@ -45,6 +45,7 @@
 #include "quickjs.h"
 #include "libregexp.h"
 #include "libunicode.h"
+#include "dtoa.h"
 
 #define OPTIMIZE         1
 #define SHORT_OPCODES    1
@@ -11129,7 +11130,9 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx,
 /* 2 <= base <= 36 */
 static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
 
-static char *u64toa(char *q, int64_t n, unsigned int base)
+/* special version going backwards */
+/* XXX: use dtoa.c */
+static char *js_u64toa(char *q, int64_t n, unsigned int base)
 {
     int digit;
     if (base == 10) {
@@ -11149,23 +11152,6 @@ static char *u64toa(char *q, int64_t n, unsigned int base)
     return q;
 }
 
-static char *i64toa(char *buf_end, int64_t n, unsigned int base)
-{
-    char *q = buf_end;
-    int is_neg;
-
-    is_neg = 0;
-    if (n < 0) {
-        is_neg = 1;
-        n = -n;
-    }
-    *--q = '\0';
-    q = u64toa(q, n, base);
-    if (is_neg)
-        *--q = '-';
-    return q;
-}
-
 /* len >= 1. 2 <= radix <= 36 */
 static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len)
 {
@@ -11226,13 +11212,13 @@ static const js_limb_t radix_base_table[JS_RADIX_MAX - 1] = {
 static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
 {
     if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) {
-        char buf[66], *q;
-        
-        q = i64toa(buf + sizeof(buf), JS_VALUE_GET_SHORT_BIG_INT(val), radix);
-        return JS_NewString(ctx, q);
+        char buf[66];
+        int len;
+        len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix);
+        return js_new_string8(ctx, (const uint8_t *)buf, len);
     } else {
         JSBigInt *r, *tmp = NULL;
-        char *buf, *q;
+        char *buf, *q, *buf_end;
         int is_neg, n_bits, log2_radix, n_digits;
         BOOL is_binary_radix;
         JSValue res;
@@ -11241,7 +11227,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
         r = JS_VALUE_GET_PTR(val);
         if (r->len == 1 && r->tab[0] == 0) {
             /* '0' case */
-            return JS_NewString(ctx, "0");
+            return js_new_string8(ctx, (const uint8_t *)"0", 1);
         }
         is_binary_radix = ((radix & (radix - 1)) == 0);
         is_neg = js_bigint_sign(r);
@@ -11271,6 +11257,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
         }
         q = buf + n_digits + is_neg + 1;
         *--q = '\0';
+        buf_end = q;
         if (!is_binary_radix) {
             int len;
             js_limb_t radix_base, v;
@@ -11283,7 +11270,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
                 if (len == 1 && r->tab[0] < radix_base) {
                     v = r->tab[0];
                     if (v != 0) {
-                        q = u64toa(q, v, radix);
+                        q = js_u64toa(q, v, radix);
                     }
                     break;
                 } else {
@@ -11313,7 +11300,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
         if (is_neg)
             *--q = '-';
         js_free(ctx, tmp);
-        res = JS_NewString(ctx, q);
+        res = js_new_string8(ctx, (const uint8_t *)q, buf_end - q);
         js_free(ctx, buf);
         return res;
     }
@@ -11333,59 +11320,6 @@ static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p)
     }
 }
 
-/* XXX: remove */
-static double js_strtod(const char *str, int radix, BOOL is_float)
-{
-    double d;
-    int c;
-
-    if (!is_float || radix != 10) {
-        const char *p = str;
-        uint64_t n_max, n;
-        int int_exp, is_neg;
-
-        is_neg = 0;
-        if (*p == '-') {
-            is_neg = 1;
-            p++;
-        }
-
-        /* skip leading zeros */
-        while (*p == '0')
-            p++;
-        n = 0;
-        if (radix == 10)
-            n_max = ((uint64_t)-1 - 9) / 10; /* most common case */
-        else
-            n_max = ((uint64_t)-1 - (radix - 1)) / radix;
-        /* XXX: could be more precise */
-        int_exp = 0;
-        while (*p != '\0') {
-            c = to_digit((uint8_t)*p);
-            if (c >= radix)
-                break;
-            if (n <= n_max) {
-                n = n * radix + c;
-            } else {
-                if (radix == 10)
-                    goto strtod_case;
-                int_exp++;
-            }
-            p++;
-        }
-        d = n;
-        if (int_exp != 0) {
-            d *= pow(radix, int_exp);
-        }
-        if (is_neg)
-            d = -d;
-    } else {
-    strtod_case:
-        d = strtod(str, NULL);
-    }
-    return d;
-}
-
 #define ATOD_INT_ONLY        (1 << 0)
 /* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
 #define ATOD_ACCEPT_BIN_OCT  (1 << 2)
@@ -11404,6 +11338,7 @@ static double js_strtod(const char *str, int radix, BOOL is_float)
 
 /* return an exception in case of memory error. Return JS_NAN if
    invalid syntax */
+/* XXX: directly use js_atod() */
 static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
                        int radix, int flags)
 {
@@ -11415,7 +11350,8 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
     int i, j, len;
     BOOL buf_allocated = FALSE;
     JSValue val;
-
+    JSATODTempMem atod_mem;
+    
     /* optional separator between digits */
     sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
     has_legacy_octal = FALSE;
@@ -11556,7 +11492,8 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
     case ATOD_TYPE_FLOAT64:
         {
             double d;
-            d = js_strtod(buf, radix, is_float);
+            d = js_atod(buf,NULL,  radix, is_float ? 0 : JS_ATOD_INT_ONLY,
+                        &atod_mem);
             /* return int or float64 */
             val = JS_NewFloat64(ctx, d);
         }
@@ -12211,320 +12148,29 @@ static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val)
     return js_bigint_to_string1(ctx, val, 10);
 }
 
-/* buf1 contains the printf result */
-static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf,
-                     int rounding_mode, char *buf1, int buf1_size)
-{
-    if (rounding_mode != FE_TONEAREST)
-        fesetround(rounding_mode);
-    snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d);
-    if (rounding_mode != FE_TONEAREST)
-        fesetround(FE_TONEAREST);
-    *sign = (buf1[0] == '-');
-    /* mantissa */
-    buf[0] = buf1[1];
-    if (n_digits > 1)
-        memcpy(buf + 1, buf1 + 3, n_digits - 1);
-    buf[n_digits] = '\0';
-    /* exponent */
-    *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1;
-}
-
-/* maximum buffer size for js_dtoa */
-#define JS_DTOA_BUF_SIZE 128
-
-/* needed because ecvt usually limits the number of digits to
-   17. Return the number of digits. */
-static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf,
-                   BOOL is_fixed)
-{
-    int rounding_mode;
-    char buf_tmp[JS_DTOA_BUF_SIZE];
-
-    if (!is_fixed) {
-        unsigned int n_digits_min, n_digits_max;
-        /* find the minimum amount of digits (XXX: inefficient but simple) */
-        n_digits_min = 1;
-        n_digits_max = 17;
-        while (n_digits_min < n_digits_max) {
-            n_digits = (n_digits_min + n_digits_max) / 2;
-            js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST,
-                     buf_tmp, sizeof(buf_tmp));
-            if (strtod(buf_tmp, NULL) == d) {
-                /* no need to keep the trailing zeros */
-                while (n_digits >= 2 && buf[n_digits - 1] == '0')
-                    n_digits--;
-                n_digits_max = n_digits;
-            } else {
-                n_digits_min = n_digits + 1;
-            }
-        }
-        n_digits = n_digits_max;
-        rounding_mode = FE_TONEAREST;
-    } else {
-        rounding_mode = FE_TONEAREST;
-#ifdef CONFIG_PRINTF_RNDN
-        {
-            char buf1[JS_DTOA_BUF_SIZE], buf2[JS_DTOA_BUF_SIZE];
-            int decpt1, sign1, decpt2, sign2;
-            /* The JS rounding is specified as round to nearest ties away
-               from zero (RNDNA), but in printf the "ties" case is not
-               specified (for example it is RNDN for glibc, RNDNA for
-               Windows), so we must round manually. */
-            js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST,
-                     buf_tmp, sizeof(buf_tmp));
-            /* XXX: could use 2 digits to reduce the average running time */
-            if (buf1[n_digits] == '5') {
-                js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD,
-                         buf_tmp, sizeof(buf_tmp));
-                js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD,
-                         buf_tmp, sizeof(buf_tmp));
-                if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) {
-                    /* exact result: round away from zero */
-                    if (sign1)
-                        rounding_mode = FE_DOWNWARD;
-                    else
-                        rounding_mode = FE_UPWARD;
-                }
-            }
-        }
-#endif /* CONFIG_PRINTF_RNDN */
-    }
-    js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode,
-             buf_tmp, sizeof(buf_tmp));
-    return n_digits;
-}
-
-static int js_fcvt1(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits,
-                    int rounding_mode)
-{
-    int n;
-    if (rounding_mode != FE_TONEAREST)
-        fesetround(rounding_mode);
-    n = snprintf(*buf, sizeof(*buf), "%.*f", n_digits, d);
-    if (rounding_mode != FE_TONEAREST)
-        fesetround(FE_TONEAREST);
-    assert(n < sizeof(*buf));
-    return n;
-}
-
-static void js_fcvt(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits)
+static JSValue js_dtoa2(JSContext *ctx,
+                        double d, int radix, int n_digits, int flags)
 {
-    int rounding_mode;
-    rounding_mode = FE_TONEAREST;
-#ifdef CONFIG_PRINTF_RNDN
-    {
-        int n1, n2;
-        char buf1[JS_DTOA_BUF_SIZE];
-        char buf2[JS_DTOA_BUF_SIZE];
-
-        /* The JS rounding is specified as round to nearest ties away from
-           zero (RNDNA), but in printf the "ties" case is not specified
-           (for example it is RNDN for glibc, RNDNA for Windows), so we
-           must round manually. */
-        n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_TONEAREST);
-        rounding_mode = FE_TONEAREST;
-        /* XXX: could use 2 digits to reduce the average running time */
-        if (buf1[n1 - 1] == '5') {
-            n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_DOWNWARD);
-            n2 = js_fcvt1(&buf2, d, n_digits + 1, FE_UPWARD);
-            if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) {
-                /* exact result: round away from zero */
-                if (buf1[0] == '-')
-                    rounding_mode = FE_DOWNWARD;
-                else
-                    rounding_mode = FE_UPWARD;
-            }
-        }
-    }
-#endif /* CONFIG_PRINTF_RNDN */
-    js_fcvt1(buf, d, n_digits, rounding_mode);
-}
-
-/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */
-/* use as many digits as necessary */
-#define JS_DTOA_VAR_FORMAT   (0 << 0)
-/* use n_digits significant digits (1 <= n_digits <= 101) */
-#define JS_DTOA_FIXED_FORMAT (1 << 0)
-/* force fractional format: [-]dd.dd with n_digits fractional digits */
-#define JS_DTOA_FRAC_FORMAT  (2 << 0)
-/* force exponential notation either in fixed or variable format */
-#define JS_DTOA_FORCE_EXP    (1 << 2)
-
-/* XXX: slow and maybe not fully correct. Use libbf when it is fast enough.
-   XXX: radix != 10 is only supported for small integers
-*/
-static void js_dtoa1(char (*buf)[JS_DTOA_BUF_SIZE], double d,
-                     int radix, int n_digits, int flags)
-{
-    char *q;
-
-    if (!isfinite(d)) {
-        if (isnan(d)) {
-            pstrcpy(*buf, sizeof(*buf), "NaN");
-        } else if (d < 0) {
-            pstrcpy(*buf, sizeof(*buf), "-Infinity");
-        } else {
-            pstrcpy(*buf, sizeof(*buf), "Infinity");
-        }
-    } else if (flags == JS_DTOA_VAR_FORMAT) {
-        int64_t i64;
-        char buf1[70], *ptr;
-        if (d > (double)MAX_SAFE_INTEGER || d < (double)-MAX_SAFE_INTEGER)
-            goto generic_conv;
-        i64 = (int64_t)d;
-        if (d != i64)
-            goto generic_conv;
-        /* fast path for integers */
-        ptr = i64toa(buf1 + sizeof(buf1), i64, radix);
-        pstrcpy(*buf, sizeof(*buf), ptr);
-    } else {
-        if (d == 0.0)
-            d = 0.0; /* convert -0 to 0 */
-        if (flags == JS_DTOA_FRAC_FORMAT) {
-            js_fcvt(buf, d, n_digits);
-        } else {
-            char buf1[JS_DTOA_BUF_SIZE];
-            int sign, decpt, k, n, i, p, n_max;
-            BOOL is_fixed;
-        generic_conv:
-            is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT);
-            if (is_fixed) {
-                n_max = n_digits;
-            } else {
-                n_max = 21;
-            }
-            /* the number has k digits (k >= 1) */
-            k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed);
-            n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */
-            q = *buf;
-            if (sign)
-                *q++ = '-';
-            if (flags & JS_DTOA_FORCE_EXP)
-                goto force_exp;
-            if (n >= 1 && n <= n_max) {
-                if (k <= n) {
-                    memcpy(q, buf1, k);
-                    q += k;
-                    for(i = 0; i < (n - k); i++)
-                        *q++ = '0';
-                    *q = '\0';
-                } else {
-                    /* k > n */
-                    memcpy(q, buf1, n);
-                    q += n;
-                    *q++ = '.';
-                    for(i = 0; i < (k - n); i++)
-                        *q++ = buf1[n + i];
-                    *q = '\0';
-                }
-            } else if (n >= -5 && n <= 0) {
-                *q++ = '0';
-                *q++ = '.';
-                for(i = 0; i < -n; i++)
-                    *q++ = '0';
-                memcpy(q, buf1, k);
-                q += k;
-                *q = '\0';
-            } else {
-            force_exp:
-                /* exponential notation */
-                *q++ = buf1[0];
-                if (k > 1) {
-                    *q++ = '.';
-                    for(i = 1; i < k; i++)
-                        *q++ = buf1[i];
-                }
-                *q++ = 'e';
-                p = n - 1;
-                if (p >= 0)
-                    *q++ = '+';
-                snprintf(q, *buf + sizeof(*buf) - q, "%d", p);
-            }
-        }
-    }
-}
-
-static JSValue js_dtoa(JSContext *ctx,
-                       double d, int radix, int n_digits, int flags)
-{
-    char buf[JS_DTOA_BUF_SIZE];
-    js_dtoa1(&buf, d, radix, n_digits, flags);
-    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';
-        }
+    char static_buf[128], *buf, *tmp_buf;
+    int len, len_max;
+    JSValue res;
+    JSDTOATempMem dtoa_mem;
+    len_max = js_dtoa_max_len(d, radix, n_digits, flags);
+    
+    /* longer buffer may be used if radix != 10 */
+    if (len_max > sizeof(static_buf) - 1) {
+        tmp_buf = js_malloc(ctx, len_max + 1);
+        if (!tmp_buf)
+            return JS_EXCEPTION;
+        buf = tmp_buf;
+    } else {
+        tmp_buf = NULL;
+        buf = static_buf;
     }
-done:
-    ptr[-1] = '-';
-    ptr -= sign;
-    return JS_NewString(ctx, ptr);
+    len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem);
+    res = js_new_string8(ctx, (const uint8_t *)buf, len);
+    js_free(ctx, tmp_buf);
+    return res;
 }
 
 JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey)
@@ -12538,9 +12184,12 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToProperty
     case JS_TAG_STRING:
         return JS_DupValue(ctx, val);
     case JS_TAG_INT:
-        snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val));
-        str = buf;
-        goto new_string;
+        {
+            int len;
+            len = i32toa(buf, JS_VALUE_GET_INT(val));
+            return js_new_string8(ctx, (const uint8_t *)buf, len);
+        }
+        break;
     case JS_TAG_BOOL:
         return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
                           JS_ATOM_true : JS_ATOM_false);
@@ -12571,8 +12220,8 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToProperty
             return JS_ThrowTypeError(ctx, "cannot convert symbol to string");
         }
     case JS_TAG_FLOAT64:
-        return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0,
-                       JS_DTOA_VAR_FORMAT);
+        return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0,
+                        JS_DTOA_FORMAT_FREE);
     case JS_TAG_SHORT_BIG_INT:
     case JS_TAG_BIG_INT:
         return js_bigint_to_string(ctx, val);
@@ -40444,7 +40093,7 @@ static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val,
                                   int argc, JSValueConst *argv, int magic)
 {
     JSValue val;
-    int base;
+    int base, flags;
     double d;
 
     val = js_thisNumberValue(ctx, this_val);
@@ -40458,16 +40107,17 @@ static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val,
             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);
+        char buf1[70];
+        int len;
+        len = i64toa_radix(buf1, JS_VALUE_GET_INT(val), base);
+        return js_new_string8(ctx, (const uint8_t *)buf1, len);
     }
     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);
+    flags = JS_DTOA_FORMAT_FREE;
+    if (base != 10)
+        flags |= JS_DTOA_EXP_DISABLED;
+    return js_dtoa2(ctx, d, base, 0, flags);
  fail:
     JS_FreeValue(ctx, val);
     return JS_EXCEPTION;
@@ -40477,7 +40127,7 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
                                  int argc, JSValueConst *argv)
 {
     JSValue val;
-    int f;
+    int f, flags;
     double d;
 
     val = js_thisNumberValue(ctx, this_val);
@@ -40489,11 +40139,11 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
         return JS_EXCEPTION;
     if (f < 0 || f > 100)
         return JS_ThrowRangeError(ctx, "invalid number of digits");
-    if (fabs(d) >= 1e21) {
-        return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
-    } else {
-        return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT);
-    }
+    if (fabs(d) >= 1e21)
+        flags = JS_DTOA_FORMAT_FREE;
+    else
+        flags = JS_DTOA_FORMAT_FRAC;
+    return js_dtoa2(ctx, d, 10, f, flags);
 }
 
 static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
@@ -40514,15 +40164,15 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
         return JS_ToStringFree(ctx,  __JS_NewFloat64(ctx, d));
     }
     if (JS_IsUndefined(argv[0])) {
-        flags = 0;
+        flags = JS_DTOA_FORMAT_FREE;
         f = 0;
     } else {
         if (f < 0 || f > 100)
             return JS_ThrowRangeError(ctx, "invalid number of digits");
         f++;
-        flags = JS_DTOA_FIXED_FORMAT;
+        flags = JS_DTOA_FORMAT_FIXED;
     }
-    return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP);
+    return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED);
 }
 
 static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
@@ -40547,7 +40197,7 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
     }
     if (p < 1 || p > 100)
         return JS_ThrowRangeError(ctx, "invalid number of digits");
-    return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT);
+    return js_dtoa2(ctx, d, 10, p, JS_DTOA_FORMAT_FIXED);
 }
 
 static const JSCFunctionListEntry js_number_proto_funcs[] = {
index 1c47f8fbf8a877103fe10f564485e838633841ef..f547037d6304959d98491f1224fdf41c8f8cfb11 100644 (file)
@@ -390,8 +390,12 @@ function test_number()
     assert((-25).toExponential(0), "-3e+1");
     assert((2.5).toPrecision(1), "3");
     assert((-2.5).toPrecision(1), "-3");
+    assert((25).toPrecision(1) === "3e+1");
     assert((1.125).toFixed(2), "1.13");
     assert((-1.125).toFixed(2), "-1.13");
+
+    assert((1.3).toString(7), "1.2046204620462046205");
+    assert((1.3).toString(35), "1.ahhhhhhhhhm");
 }
 
 function test_eval2()