From 993660621a0a834c93eaad68713b2dc25508b326 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Wed, 19 Mar 2025 19:07:57 +0100 Subject: [PATCH] added new dtoa library to print and parse float64 numbers. It is necessary to fix corner cases (e.g. radix != 10) and to have correct behavior regardless of the libc implementation. --- Makefile | 2 +- dtoa.c | 1626 +++++++++++++++++++++++++++++++++++++++++ dtoa.h | 83 +++ quickjs.c | 480 ++---------- tests/test_builtin.js | 4 + 5 files changed, 1779 insertions(+), 416 deletions(-) create mode 100644 dtoa.c create mode 100644 dtoa.h diff --git a/Makefile b/Makefile index 8d134c8..8314cb7 100644 --- 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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> 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 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); diff --git a/quickjs.c b/quickjs.c index f8c6d89..831f961 100644 --- 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) +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) { - 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; + 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 { - 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 */ + tmp_buf = NULL; + buf = static_buf; } - 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) -{ - 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'; - } - } -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[] = { diff --git a/tests/test_builtin.js b/tests/test_builtin.js index 1c47f8f..f547037 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -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()