From af0d0242f2bf184466f7f2d8cc64d88fa7f004aa Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:55:55 -0400 Subject: [PATCH 1/5] Port bellard/quickjs dtoa library commits 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. Port of: bellard/quickjs@993660621a0a834c93eaad68713b2dc25508b326 bellard/quickjs@dbbca3dbf3856938120071225a5e4c906d3177e8 Includes alternative fix for bellard/quickjs@638ec8c from 0191aea Update CMakeLists.txt and meson.build Co-Authored-By: Fabrice Bellard --- CMakeLists.txt | 1 + cutils.c | 252 ------- cutils.h | 10 - dtoa.c | 1620 +++++++++++++++++++++++++++++++++++++++++ dtoa.h | 83 +++ meson.build | 1 + quickjs.c | 963 +++++++----------------- tests/test_builtin.js | 5 + 8 files changed, 1986 insertions(+), 949 deletions(-) create mode 100644 dtoa.c create mode 100644 dtoa.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 91c6c683e..538072798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,6 +227,7 @@ endmacro() set(qjs_sources cutils.c + dtoa.c libregexp.c libunicode.c quickjs.c diff --git a/cutils.c b/cutils.c index 65f23d077..b41eda355 100644 --- a/cutils.c +++ b/cutils.c @@ -586,258 +586,6 @@ size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_ return j; } -/*--- integer to string conversions --*/ - -/* All conversion functions: - - require a destination array `buf` of sufficient length - - write the string representation at the beginning of `buf` - - null terminate the string - - return the string length - */ - -/* 2 <= base <= 36 */ -char const digits36[36] = { - '0','1','2','3','4','5','6','7','8','9', - 'a','b','c','d','e','f','g','h','i','j', - 'k','l','m','n','o','p','q','r','s','t', - 'u','v','w','x','y','z' -}; - - -#define USE_SPECIAL_RADIX_10 1 // special case base 10 radix conversions -#define USE_SINGLE_CASE_FAST 1 // special case single digit numbers - -/* using u32toa_shift variant */ - -#define gen_digit(buf, c) if (is_be()) \ - buf = (buf >> 8) | ((uint64_t)(c) << ((sizeof(buf) - 1) * 8)); \ - else \ - buf = (buf << 8) | (c) - -static size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) -{ - size_t len = 1; - uint64_t buf = 0; - while (n >= 10) { - uint32_t quo = n % 10; - n /= 10; - gen_digit(buf, '0' + quo); - len++; - } - gen_digit(buf, '0' + n); - memcpy(dest, &buf, sizeof buf); - return len; -} - -static size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) -{ - size_t i; - dest += len; - dest[7] = '\0'; - for (i = 7; i-- > 1;) { - uint32_t quo = n % 10; - n /= 10; - dest[i] = (char)('0' + quo); - } - dest[i] = (char)('0' + n); - return len + 7; -} - -size_t u32toa(char buf[minimum_length(11)], uint32_t n) -{ -#ifdef USE_SINGLE_CASE_FAST /* 10% */ - if (n < 10) { - buf[0] = (char)('0' + n); - buf[1] = '\0'; - return 1; - } -#endif -#define TEN_POW_7 10000000 - if (n >= TEN_POW_7) { - uint32_t quo = n / TEN_POW_7; - n %= TEN_POW_7; - size_t len = u7toa_shift(buf, quo); - return u07toa_shift(buf, n, len); - } - return u7toa_shift(buf, n); -} - -size_t u64toa(char buf[minimum_length(21)], uint64_t n) -{ - if (likely(n < 0x100000000)) - return u32toa(buf, n); - - size_t len; - if (n >= TEN_POW_7) { - uint64_t n1 = n / TEN_POW_7; - n %= TEN_POW_7; - if (n1 >= TEN_POW_7) { - uint32_t quo = n1 / TEN_POW_7; - n1 %= TEN_POW_7; - len = u7toa_shift(buf, quo); - len = u07toa_shift(buf, n1, len); - } else { - len = u7toa_shift(buf, n1); - } - return u07toa_shift(buf, n, len); - } - return u7toa_shift(buf, n); -} - -size_t i32toa(char buf[minimum_length(12)], int32_t n) -{ - if (likely(n >= 0)) - return u32toa(buf, n); - - buf[0] = '-'; - return 1 + u32toa(buf + 1, -(uint32_t)n); -} - -size_t i64toa(char buf[minimum_length(22)], int64_t n) -{ - if (likely(n >= 0)) - return u64toa(buf, n); - - buf[0] = '-'; - return 1 + u64toa(buf + 1, -(uint64_t)n); -} - -/* using u32toa_radix_length variant */ - -static uint8_t const radix_shift[64] = { - 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base) -{ - int shift; - -#ifdef USE_SPECIAL_RADIX_10 - if (likely(base == 10)) - return u32toa(buf, n); -#endif - if (n < base) { - buf[0] = digits36[n]; - buf[1] = '\0'; - return 1; - } - shift = radix_shift[base & 63]; - if (shift) { - uint32_t mask = (1 << shift) - 1; - size_t len = (32 - clz32(n) + shift - 1) / shift; - size_t last = n & mask; - char *end = buf + len; - n >>= shift; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n & mask; - n >>= shift; - *end-- = digits36[quo]; - } - *end = digits36[n]; - return len; - } else { - size_t len = 2; - size_t last = n % base; - n /= base; - uint32_t nbase = base; - while (n >= nbase) { - nbase *= base; - len++; - } - char *end = buf + len; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n % base; - n /= base; - *end-- = digits36[quo]; - } - *end = digits36[n]; - return len; - } -} - -size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base) -{ - int shift; - -#ifdef USE_SPECIAL_RADIX_10 - if (likely(base == 10)) - return u64toa(buf, n); -#endif - shift = radix_shift[base & 63]; - if (shift) { - if (n < base) { - buf[0] = digits36[n]; - buf[1] = '\0'; - return 1; - } - uint64_t mask = (1 << shift) - 1; - size_t len = (64 - clz64(n) + shift - 1) / shift; - size_t last = n & mask; - char *end = buf + len; - n >>= shift; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n & mask; - n >>= shift; - *end-- = digits36[quo]; - } - *end = digits36[n]; - return len; - } else { - if (likely(n < 0x100000000)) - return u32toa_radix(buf, n, base); - size_t last = n % base; - n /= base; - uint64_t nbase = base; - size_t len = 2; - while (n >= nbase) { - nbase *= base; - len++; - } - char *end = buf + len; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n % base; - n /= base; - *end-- = digits36[quo]; - } - *end = digits36[n]; - return len; - } -} - -size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned int base) -{ - if (likely(n >= 0)) - return u32toa_radix(buf, n, base); - - buf[0] = '-'; - return 1 + u32toa_radix(buf + 1, -(uint32_t)n, base); -} - -size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base) -{ - if (likely(n >= 0)) - return u64toa_radix(buf, n, base); - - buf[0] = '-'; - return 1 + u64toa_radix(buf + 1, -(uint64_t)n, base); -} - -#undef gen_digit -#undef TEN_POW_7 -#undef USE_SPECIAL_RADIX_10 -#undef USE_SINGLE_CASE_FAST - /*---- sorting with opaque argument ----*/ typedef void (*exchange_f)(void *a, void *b, size_t size); diff --git a/cutils.h b/cutils.h index ec1fa886a..5129c3cb2 100644 --- a/cutils.h +++ b/cutils.h @@ -543,16 +543,6 @@ static inline uint8_t to_upper_ascii(uint8_t c) { return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; } -extern char const digits36[36]; -size_t u32toa(char buf[minimum_length(11)], uint32_t n); -size_t i32toa(char buf[minimum_length(12)], int32_t n); -size_t u64toa(char buf[minimum_length(21)], uint64_t n); -size_t i64toa(char buf[minimum_length(22)], int64_t n); -size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned int base); -size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned base); -size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned int base); -size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base); - void rqsort(void *base, size_t nmemb, size_t size, int (*cmp)(const void *, const void *, void *), void *arg); diff --git a/dtoa.c b/dtoa.c new file mode 100644 index 000000000..e145068c5 --- /dev/null +++ b/dtoa.c @@ -0,0 +1,1620 @@ +/* + * 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; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* 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 */ + 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); + + /* 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: + 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) && js__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 = false; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = true; + 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 000000000..de76f1a3f --- /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/meson.build b/meson.build index af20c64a6..6c1d9f396 100644 --- a/meson.build +++ b/meson.build @@ -138,6 +138,7 @@ qjs_sys_deps += dependency('dl', required: false) qjs_srcs = files( 'cutils.c', + 'dtoa.c', 'libregexp.c', 'libunicode.c', 'quickjs.c', diff --git a/quickjs.c b/quickjs.c index 378967211..0d691e58f 100644 --- a/quickjs.c +++ b/quickjs.c @@ -48,6 +48,7 @@ #include "quickjs.h" #include "libregexp.h" #include "xsum.h" +#include "dtoa.h" #if defined(EMSCRIPTEN) || defined(_MSC_VER) #define DIRECT_DISPATCH 0 @@ -11951,186 +11952,152 @@ 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; -} - -/* `js_atof(ctx, p, len, pp, radix, flags)` - Convert the string pointed to by `p` to a number value. - Return an exception in case of memory error. - Return `JS_NAN` if invalid syntax. - - `p` points to a null terminated UTF-8 encoded char array, - - `len` the length of the array, - - `pp` if not null receives a pointer to the next character, - - `radix` must be in range 2 to 36, else return `JS_NAN`. - - `flags` is a combination of the flags below. - There is a null byte at `p[len]`, but there might be embedded null - bytes between `p[0]` and `p[len]` which must produce `JS_NAN` if - the `ATOD_NO_TRAILING_CHARS` flag is present. - */ - -#define ATOD_TRIM_SPACES (1 << 0) /* trim white space */ -#define ATOD_ACCEPT_EMPTY (1 << 1) /* accept an empty string, value is 0 */ -#define ATOD_ACCEPT_FLOAT (1 << 2) /* parse decimal floating point syntax */ -#define ATOD_ACCEPT_INFINITY (1 << 3) /* parse Infinity as a float point number */ -#define ATOD_ACCEPT_BIN_OCT (1 << 4) /* accept 0o and 0b prefixes */ -#define ATOD_ACCEPT_HEX_PREFIX (1 << 5) /* accept 0x prefix for radix 16 */ -#define ATOD_ACCEPT_UNDERSCORES (1 << 6) /* accept _ between digits as a digit separator */ -#define ATOD_ACCEPT_SUFFIX (1 << 7) /* allow 'n' suffix to produce BigInt */ -#define ATOD_WANT_BIG_INT (1 << 8) /* return type must be BigInt */ -#define ATOD_DECIMAL_AFTER_SIGN (1 << 9) /* only accept decimal number after sign */ -#define ATOD_NO_TRAILING_CHARS (1 << 10) /* do not accept trailing characters */ - -static JSValue js_atof(JSContext *ctx, const char *p, size_t len, - const char **pp, int radix, int flags) -{ - const char *p_start; - const char *end = p + len; - int sep; - bool is_float; - char buf1[64], *buf = buf1; - size_t i, j; - JSValue val = JS_NAN; - double d; - char sign; - - if (radix < 2 || radix > 36) - goto done; - +#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) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +/* 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) +{ + const char *p, *p_start; + int sep, is_neg; + bool is_float, has_legacy_octal; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + bool buf_allocated = false; + JSValue val; + JSATODTempMem atod_mem; + /* optional separator between digits */ sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; - sign = 0; - if (flags & ATOD_TRIM_SPACES) - p += skip_spaces(p); - if (p == end && (flags & ATOD_ACCEPT_EMPTY)) { - if (pp) *pp = p; - if (flags & ATOD_WANT_BIG_INT) - return JS_NewBigInt64(ctx, 0); - else - return js_int32(0); - } - if (*p == '+' || *p == '-') { - sign = *p; + has_legacy_octal = false; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } else if (p[0] == '-') { p++; - if (flags & ATOD_DECIMAL_AFTER_SIGN) - flags &= ~(ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT); + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; } if (p[0] == '0') { if ((p[1] == 'x' || p[1] == 'X') && - ((flags & ATOD_ACCEPT_HEX_PREFIX) || radix == 16)) { + (radix == 0 || radix == 16)) { p += 2; radix = 16; - } else if (flags & ATOD_ACCEPT_BIN_OCT) { - if (p[1] == 'o' || p[1] == 'O') { - p += 2; - radix = 8; - } else if (p[1] == 'b' || p[1] == 'B') { - p += 2; - radix = 2; - } + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + has_legacy_octal = true; + 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 (*p == 'I' && (flags & ATOD_ACCEPT_INFINITY) && js__strstart(p, "Infinity", &p)) { - d = INF; - if (sign == '-') + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && + (atod_type == ATOD_TYPE_FLOAT64) && + js__strstart(p, "Infinity", &p)) { + double d = INF; + if (is_neg) d = -d; - val = js_float64(d); + val = JS_NewFloat64(ctx, d); goto done; } } + if (radix == 0) + radix = 10; is_float = false; p_start = p; - while (to_digit(*p) < radix) { + while (to_digit((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || + p != p_start + 1 || p[-1] != '0') && + to_digit((uint8_t)p[1]) < radix)) { p++; - if (*p == sep && to_digit(p[1]) < radix) - p++; } - if ((flags & ATOD_ACCEPT_FLOAT) && radix == 10) { - if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) { + if (!(flags & ATOD_INT_ONLY)) { + if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { is_float = true; p++; - while (to_digit(*p) < radix) { + if (*p == sep) + goto fail; + while (to_digit((uint8_t)*p) < radix || + (*p == sep && to_digit((uint8_t)p[1]) < radix)) p++; - if (*p == sep && to_digit(p[1]) < radix) - p++; - } } - if (p > p_start && (*p == 'e' || *p == 'E')) { - i = 1; - if (p[1] == '+' || p[1] == '-') { - i++; - } - if (is_digit(p[i])) { - is_float = true; - p += i + 1; - while (is_digit(*p) || (*p == sep && is_digit(p[1]))) + if (p > p_start && + (((*p == 'e' || *p == 'E') && radix == 10) || + ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + const char *p1 = p + 1; + is_float = true; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) p++; } } } if (p == p_start) - goto done; + goto fail; + buf = buf1; + buf_allocated = false; len = p - p_start; if (unlikely((len + 2) > sizeof(buf1))) { buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ - if (!buf) { - if (pp) *pp = p; - return JS_ThrowOutOfMemory(ctx); - } + if (!buf) + goto mem_error; + buf_allocated = true; } - /* remove the separators and the radix prefix */ + /* remove the separators and the radix prefixes */ j = 0; - if (sign == '-') + if (is_neg) buf[j++] = '-'; for (i = 0; i < len; i++) { if (p_start[i] != '_') @@ -12141,38 +12108,57 @@ static JSValue js_atof(JSContext *ctx, const char *p, size_t len, if (flags & ATOD_ACCEPT_SUFFIX) { if (*p == 'n') { p++; - flags |= ATOD_WANT_BIG_INT; + atod_type = ATOD_TYPE_BIG_INT; + } else { + if (is_float && radix != 10) + goto fail; + } + } else { + if (atod_type == ATOD_TYPE_FLOAT64) { + if (is_float && radix != 10) + goto fail; } } - if (flags & ATOD_WANT_BIG_INT) { - JSBigInt *r; - if (!is_float) { + switch(atod_type) { + case ATOD_TYPE_FLOAT64: + { + double d; + d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, + &atod_mem); + /* return int or float64 */ + val = JS_NewFloat64(ctx, d); + } + break; + case ATOD_TYPE_BIG_INT: + { + JSBigInt *r; + if (has_legacy_octal || is_float) + goto fail; r = js_bigint_from_string(ctx, buf, radix); if (!r) { - val = JS_ThrowOutOfMemory(ctx); + val = JS_EXCEPTION; goto done; } val = JS_CompactBigInt(ctx, r); } - } else { - d = js_strtod(buf, radix, is_float); - val = js_number(d); /* return int or float64 */ + break; + default: + abort(); } - done: - if (flags & ATOD_NO_TRAILING_CHARS) { - if (flags & ATOD_TRIM_SPACES) - p += skip_spaces(p); - if (p != end) { - JS_FreeValue(ctx, val); - val = JS_NAN; - } - } - if (buf != buf1) +done: + if (buf_allocated) js_free_rt(ctx->rt, buf); - if (pp) *pp = p; + if (pp) + *pp = p; return val; + fail: + val = JS_NAN; + goto done; + mem_error: + val = JS_ThrowOutOfMemory(ctx); + goto done; } typedef enum JSToNumberHintEnum { @@ -12217,19 +12203,28 @@ static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, case JS_TAG_STRING: { const char *str; + const char *p; size_t len; - int flags; str = JS_ToCStringLen(ctx, &len, val); JS_FreeValue(ctx, val); if (!str) return JS_EXCEPTION; - // TODO(saghul): Sync with bellard/quickjs ? - flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | - ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY | - ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; - ret = js_atof(ctx, str, len, NULL, 10, flags); + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + ret = JS_NewInt32(ctx, 0); + } else { + int flags = ATOD_ACCEPT_BIN_OCT; + ret = js_atof(ctx, p, &p, 0, flags); + if (!JS_IsException(ret)) { + p += skip_spaces(p); + if ((p - str) != len) { + JS_FreeValue(ctx, ret); + ret = JS_NAN; + } + } + } JS_FreeCString(ctx, str); } break; @@ -12791,425 +12786,29 @@ static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) /*---- floating point number to string conversions ----*/ -/* JavaScript rounding is specified as round to nearest tie away - from zero (RNDNA), but in `printf` the "ties" case is not - specified (in most cases it is RNDN, round to nearest, tie to even), - so we must round manually. We generate 2 extra places and make - an extra call to snprintf if these are exactly '50'. - We set the current rounding mode to FE_DOWNWARD to check if the - last 2 places become '49'. If not, we must round up, which is - performed in place using the string digits. - - Note that we cannot rely on snprintf for rounding up: - the code below fails on macOS for `0.5.toFixed(0)`: gives `0` expected `1` - fesetround(FE_UPWARD); - snprintf(dest, size, "%.*f", n_digits, d); - fesetround(FE_TONEAREST); - */ - -/* `js_fcvt` minimum buffer length: - - up to 21 digits in integral part - - 1 potential decimal point - - up to 102 decimals - - 1 null terminator - */ -#define JS_FCVT_BUF_SIZE (21+1+102+1) - -/* `js_ecvt` minimum buffer length: - - 1 leading digit - - 1 potential decimal point - - up to 102 decimals - - 5 exponent characters (from 'e-324' to 'e+308') - - 1 null terminator - */ -#define JS_ECVT_BUF_SIZE (1+1+102+5+1) - -/* `js_dtoa` minimum buffer length: - - 8 byte prefix - - either JS_FCVT_BUF_SIZE or JS_ECVT_BUF_SIZE - - JS_FCVT_BUF_SIZE is larger than JS_ECVT_BUF_SIZE - */ -#define JS_DTOA_BUF_SIZE (8+JS_FCVT_BUF_SIZE) - -/* `js_ecvt1`: compute the digits and decimal point spot for a double - - `d` is finite, positive or zero - - `n_digits` number of significant digits in range 1..103 - - `buf` receives the printf result - - `buf` has a fixed format: n_digits with a decimal point at offset 1 - and exponent 'e{+/-}xx[x]' at offset n_digits+1 - Return n_digits - Store the position of the decimal point into `*decpt` - */ -static int js_ecvt1(double d, int n_digits, - char dest[minimum_length(JS_ECVT_BUF_SIZE)], - size_t size, int *decpt) -{ - /* d is positive, ensure decimal point is always present */ - snprintf(dest, size, "%#.*e", n_digits - 1, d); - /* dest contents: - 0: first digit - 1: '.' decimal point (locale specific) - 2..n_digits: (n_digits-1) additional digits - n_digits+1: 'e' exponent mark - n_digits+2..: exponent sign, value and null terminator - */ - /* extract the exponent (actually the position of the decimal point) */ - *decpt = 1 + atoi(dest + n_digits + 2); - return n_digits; -} - -/* `js_ecvt`: compute the digits and decimal point spot for a double - with proper javascript rounding. We cannot use `ecvt` for multiple - resasons: portability, because of the number of digits is typically - limited to 17, finally because the default rounding is inadequate. - `d` is finite and positive or zero. - `n_digits` number of significant digits in range 1..101 - or 0 for automatic (only as many digits as necessary) - Return the number of digits produced in `dest`. - Store the position of the decimal point into `*decpt` - */ -static int js_ecvt(double d, int n_digits, - char dest[minimum_length(JS_ECVT_BUF_SIZE)], - size_t size, int *decpt) -{ - if (n_digits == 0) { - /* find the minimum number of digits (XXX: inefficient but simple) */ - // TODO(chqrlie) use direct method from quickjs-printf - unsigned int n_digits_min = 1; - unsigned int n_digits_max = 17; - for (;;) { - n_digits = (n_digits_min + n_digits_max) / 2; - js_ecvt1(d, n_digits, dest, size, decpt); - if (n_digits_min == n_digits_max) - return n_digits; - /* dest contents: - 0: first digit - 1: '.' decimal point (locale specific) - 2..n_digits: (n_digits-1) additional digits - n_digits+1: 'e' exponent mark - n_digits+2..: exponent sign, value and null terminator - */ - if (strtod(dest, NULL) == d) { - unsigned int n0 = n_digits; - /* enough digits */ - /* strip the trailing zeros */ - while (dest[n_digits] == '0') - n_digits--; - if (n_digits == n_digits_min) - return n_digits; - /* done if trailing zeros and not denormal or huge */ - if (n_digits < n0 && d > 3e-308 && d < 8e307) - return n_digits; - n_digits_max = n_digits; - } else { - /* need at least one more digit */ - n_digits_min = n_digits + 1; - } - } - } else { -#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) - int i; - /* generate 2 extra digits: 99% chances to avoid 2 calls */ - js_ecvt1(d, n_digits + 2, dest, size, decpt); - if (dest[n_digits + 1] < '5') - return n_digits; /* truncate the 2 extra digits */ - if (dest[n_digits + 1] == '5' && dest[n_digits + 2] == '0') { - /* close to half-way: try rounding toward 0 */ - fesetround(FE_DOWNWARD); - js_ecvt1(d, n_digits + 2, dest, size, decpt); - fesetround(FE_TONEAREST); - if (dest[n_digits + 1] < '5') - return n_digits; /* truncate the 2 extra digits */ - } - /* round up in the string */ - for(i = n_digits;; i--) { - /* ignore the locale specific decimal point */ - if (is_digit(dest[i])) { - if (dest[i]++ < '9') - break; - dest[i] = '0'; - if (i == 0) { - dest[0] = '1'; - (*decpt)++; - break; - } - } - } - return n_digits; /* truncate the 2 extra digits */ -#else - /* No disambiguation available, eg: __wasi__ targets */ - return js_ecvt1(d, n_digits, dest, size, decpt); -#endif - } -} - -/* `js_fcvt`: convert a floating point value to %f format using RNDNA - `d` is finite and positive or zero. - `n_digits` number of decimal places in range 0..100 - Return the number of characters produced in `dest`. - */ -static size_t js_fcvt(double d, int n_digits, - char dest[minimum_length(JS_FCVT_BUF_SIZE)], size_t size) -{ -#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) - int i, n1; - /* generate 2 extra digits: 99% chances to avoid 2 calls */ - n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; - if (dest[n1] >= '5') { - if (dest[n1] == '5' && dest[n1 + 1] == '0') { - /* close to half-way: try rounding toward 0 */ - fesetround(FE_DOWNWARD); - n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; - fesetround(FE_TONEAREST); - } - if (dest[n1] >= '5') { /* number should be rounded up */ - /* d is either exactly half way or greater: round the string manually */ - for (i = n1 - 1;; i--) { - /* ignore the locale specific decimal point */ - if (is_digit(dest[i])) { - if (dest[i]++ < '9') - break; - dest[i] = '0'; - if (i == 0) { - dest[0] = '1'; - dest[n1] = '0'; - dest[n1 - n_digits - 1] = '0'; - dest[n1 - n_digits] = '.'; - n1++; - break; - } - } - } - } - } - /* truncate the extra 2 digits and the decimal point if !n_digits */ - n1 -= !n_digits; - //dest[n1] = '\0'; // optional - return n1; -#else - /* No disambiguation available, eg: __wasi__ targets */ - return snprintf(dest, size, "%.*f", n_digits, d); -#endif -} - -static JSValue js_dtoa_infinite(JSContext *ctx, double d) +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) { - // TODO(chqrlie) use atoms for NaN and Infinite? - if (isnan(d)) - return js_new_string8(ctx, "NaN"); - if (d < 0) - return js_new_string8(ctx, "-Infinity"); - else - return js_new_string8(ctx, "Infinity"); -} - -#define JS_DTOA_TOSTRING 0 /* use as many digits as necessary */ -#define JS_DTOA_EXPONENTIAL 1 /* use exponential notation either fixed or variable digits */ -#define JS_DTOA_FIXED 2 /* force fixed number of fractional digits */ -#define JS_DTOA_PRECISION 3 /* use n_digits significant digits (1 <= n_digits <= 101) */ - -/* `js_dtoa`: convert a floating point number to a string - - `mode`: one of the 4 supported formats - - `n_digits`: digit number according to mode - - TOSTRING: 0 only. As many digits as necessary - - EXPONENTIAL: 0 as many decimals as necessary - - 1..101 number of significant digits - - FIXED: 0..100 number of decimal places - - PRECISION: 1..101 number of significant digits - */ -// XXX: should use libbf or quickjs-printf. -static JSValue js_dtoa(JSContext *ctx, double d, int n_digits, int mode) -{ - char buf[JS_DTOA_BUF_SIZE]; - size_t len; - char *start; - int sign, decpt, exp, i, k, n, n_max; - - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - - sign = (d < 0); - start = buf + 8; - d = fabs(d); /* also converts -0 to 0 */ - - if (mode != JS_DTOA_EXPONENTIAL && n_digits == 0) { - /* fast path for exact integers in variable format: - clip to MAX_SAFE_INTEGER because to ensure insignificant - digits are generated as 0. - used for JS_DTOA_TOSTRING and JS_DTOA_FIXED without decimals. - */ - if (d <= (double)MAX_SAFE_INTEGER) { - uint64_t u64 = (uint64_t)d; - if (d == u64) { - len = u64toa(start, u64); - goto done; - } - } - } - if (mode == JS_DTOA_FIXED) { - len = js_fcvt(d, n_digits, start, sizeof(buf) - 8); - // TODO(chqrlie) patch the locale specific decimal point - goto done; - } - - n_max = (n_digits > 0) ? n_digits : 21; - /* the number has k digits (1 <= k <= n_max) */ - k = js_ecvt(d, n_digits, start, sizeof(buf) - 8, &decpt); - /* buffer contents: - 0: first digit - 1: '.' decimal point - 2..k: (k-1) additional digits - */ - n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ - if (mode != JS_DTOA_EXPONENTIAL) { - /* mode is JS_DTOA_PRECISION or JS_DTOA_TOSTRING */ - if (n >= 1 && n <= n_max) { - /* between 1 and n_max digits before the decimal point */ - if (k <= n) { - /* all digits before the point, append zeros */ - start[1] = start[0]; - start++; - for(i = k; i < n; i++) - start[i] = '0'; - len = n; - } else { - /* k > n: move digits before the point */ - for(i = 1; i < n; i++) - start[i] = start[i + 1]; - start[i] = '.'; - len = 1 + k; - } - goto done; - } - if (n >= -5 && n <= 0) { - /* insert -n leading 0 decimals and a '0.' prefix */ - n = -n; - start[1] = start[0]; - start -= n + 1; - start[0] = '0'; - start[1] = '.'; - for(i = 0; i < n; i++) - start[2 + i] = '0'; - len = 2 + k + n; - goto done; - } - } - /* exponential notation */ - exp = n - 1; - /* count the digits and the decimal point if at least one decimal */ - len = k + (k > 1); - start[1] = '.'; /* patch the locale specific decimal point */ - start[len] = 'e'; - start[len + 1] = '+'; - if (exp < 0) { - start[len + 1] = '-'; - exp = -exp; - } - len += 2 + 1 + (exp > 9) + (exp > 99); - for (i = len - 1; exp > 9;) { - int quo = exp / 10; - start[i--] = (char)('0' + exp % 10); - exp = quo; - } - start[i] = (char)('0' + exp); - - done: - start[-1] = '-'; /* prepend the sign if negative */ - return js_new_string8_len(ctx, start - sign, len + sign); -} - -/* `js_dtoa_radix`: convert a floating point number using a specific base - - `d` must be finite - - `radix` must be in range 2..36 - */ -static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix) -{ - char buf[2200], *ptr, *ptr2, *ptr3; - int sign, digit; - double frac, d0; - int64_t n0; - - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - - sign = (d < 0); - d = fabs(d); - d0 = trunc(d); - n0 = 0; - frac = d - d0; - ptr2 = buf + 1100; /* ptr2 points to the end of the string */ - ptr = ptr2; /* ptr points to the beginning of the string */ - if (d0 <= MAX_SAFE_INTEGER) { - int64_t n = n0 = (int64_t)d0; - while (n >= radix) { - digit = n % radix; - n = n / radix; - *--ptr = digits36[digit]; - } - *--ptr = digits36[(size_t)n]; - } else { - /* no decimals */ - while (d0 >= radix) { - digit = fmod(d0, radix); - d0 = trunc(d0 / radix); - if (d0 >= MAX_SAFE_INTEGER) - digit = 0; - *--ptr = digits36[digit]; - } - *--ptr = digits36[(size_t)d0]; - goto done; - } - if (frac != 0) { - double log2_radix = log2(radix); - double prec = 1023 + 51; // handle subnormals - *ptr2++ = '.'; - while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) { - frac *= radix; - digit = trunc(frac); - frac -= digit; - *ptr2++ = digits36[digit]; - n0 = n0 * radix + digit; - prec -= log2_radix; - } - if (frac * radix >= radix / 2) { - /* round up the string representation manually */ - char nine = digits36[radix - 1]; - while (ptr2[-1] == nine) { - /* strip trailing '9' or equivalent digits */ - ptr2--; - } - if (ptr2[-1] == '.') { - /* strip the 'decimal' point */ - ptr2--; - /* increment the integral part */ - for (ptr3 = ptr2;;) { - if (ptr3[-1] != nine) { - ptr3[-1] = (ptr3[-1] == '9') ? 'a' : ptr3[-1] + 1; - break; - } - *--ptr3 = '0'; - if (ptr3 <= ptr) { - /* prepend a '1' if number was all nines */ - *--ptr = '1'; - break; - } - } - } else { - /* increment the last fractional digit */ - ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1; - } - } else { - /* strip trailing fractional zeros */ - while (ptr2[-1] == '0') - ptr2--; - /* strip the 'decimal' point if last */ - ptr2 -= (ptr2[-1] == '.'); - } + 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_new_string8_len(ctx, ptr, ptr2 - ptr); + len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len(ctx, buf, len); + js_free(ctx, tmp_buf); + return res; } JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, @@ -13255,7 +12854,8 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); } case JS_TAG_FLOAT64: - return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 0, JS_DTOA_TOSTRING); + 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); @@ -13671,7 +13271,7 @@ JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) /* return NaN if bad bigint literal */ static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) { - const char *str; + const char *str, *p; size_t len; int flags; @@ -13679,12 +13279,21 @@ static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) JS_FreeValue(ctx, val); if (!str) return JS_EXCEPTION; - // TODO(saghul): sync with bellard/quickjs ? - flags = ATOD_WANT_BIG_INT | - ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | - ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; - val = js_atof(ctx, str, len, NULL, 10, flags); + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + val = JS_NewBigInt64(ctx, 0); + } else { + flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; + val = js_atof(ctx, p, &p, 0, flags); + p += skip_spaces(p); + if (!JS_IsException(val)) { + if ((p - str) != len) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + } JS_FreeCString(ctx, str); return val; } @@ -21029,7 +20638,6 @@ static __exception int next_token(JSParseState *s) int c; bool ident_has_escape; JSAtom atom; - int flags, radix; if (js_check_stack_overflow(s->ctx->rt, 1000)) { JS_ThrowStackOverflow(s->ctx); @@ -21224,56 +20832,36 @@ static __exception int next_token(JSParseState *s) break; } if (p[1] >= '0' && p[1] <= '9') { - flags = ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_FLOAT; - radix = 10; goto parse_number; + } else { + goto def_token; } - goto def_token; + break; case '0': - if (is_digit(p[1])) { /* handle legacy octal */ - if (s->cur_func->is_strict_mode) { - js_parse_error(s, "Octal literals are not allowed in strict mode"); - goto fail; - } - /* Legacy octal: no separators, no suffix, no floats, - base 8 unless non octal digits are detected */ - flags = 0; - radix = 8; - while (is_digit(*p)) { - if (*p >= '8' && *p <= '9') - radix = 10; - p++; - } - p = s->token.ptr; - goto parse_number; - } - if (p[1] == '_') { - js_parse_error(s, "Numeric separator can not be used after leading 0"); + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->is_strict_mode)) { + js_parse_error(s, "Octal literals are not allowed in strict mode"); goto fail; } - flags = ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - radix = 10; goto parse_number; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* number */ + parse_number: { JSValue ret; - const char *p1; - - flags = ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - radix = 10; - parse_number: - p1 = (const char *)p; - ret = js_atof(s->ctx, p1, s->buf_end - p, &p1, radix, flags); - p = (const uint8_t *)p1; + const uint8_t *p1; + int flags; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, + flags); if (JS_IsException(ret)) goto fail; /* reject `10instanceof Number` */ if (JS_VALUE_IS_NAN(ret) || - lre_js_is_ident_next(utf8_decode(p, &p_next))) { + lre_js_is_ident_next(utf8_decode(p, &p1))) { JS_FreeValue(s->ctx, ret); js_parse_error(s, "invalid number literal"); goto fail; @@ -42744,44 +42332,42 @@ static int js_get_radix(JSContext *ctx, JSValueConst val) static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { - char buf[72]; JSValue val; - int base; + int base, flags; double d; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) return val; if (magic || JS_IsUndefined(argv[0])) { - if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { - size_t len = i32toa(buf, JS_VALUE_GET_INT(val)); - return js_new_string8_len(ctx, buf, len); - } base = 10; } else { base = js_get_radix(ctx, argv[0]); - if (base < 0) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } + if (base < 0) + goto fail; } if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { - size_t len = i32toa_radix(buf, JS_VALUE_GET_INT(val), base); - return js_new_string8_len(ctx, buf, len); + char buf1[70]; + int len; + len = i64toa_radix(buf1, JS_VALUE_GET_INT(val), base); + return js_new_string8_len(ctx, buf1, len); } if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; + flags = JS_DTOA_FORMAT_FREE; if (base != 10) - return js_dtoa_radix(ctx, d, base); - - return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); + flags |= JS_DTOA_EXP_DISABLED; + return js_dtoa2(ctx, d, base, 0, flags); + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; } 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); @@ -42791,23 +42377,21 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; - if (f < 0 || f > 100) { - return JS_ThrowRangeError(ctx, "toFixed() digits argument must be between 0 and 100"); - } - if (fabs(d) >= 1e21) { - // use ToString(d) - return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); - } else { - return js_dtoa(ctx, d, f, JS_DTOA_FIXED); - } + if (f < 0 || f > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + 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, int argc, JSValueConst *argv) { JSValue val; + int f, flags; double d; - int f; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) @@ -42816,15 +42400,19 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - if (!JS_IsUndefined(argv[0])) { - if (f < 0 || f > 100) { - return JS_ThrowRangeError(ctx, "toExponential() argument must be between 0 and 100"); - } - f += 1; /* number of significant digits between 1 and 101 */ + if (!isfinite(d)) { + return JS_ToStringFree(ctx, __JS_NewFloat64(d)); + } + if (JS_IsUndefined(argv[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_FORMAT_FIXED; } - return js_dtoa(ctx, d, f, JS_DTOA_EXPONENTIAL); + return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED); } static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val, @@ -42840,15 +42428,16 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val, if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; if (JS_IsUndefined(argv[0])) - return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); + goto to_string; if (JS_ToInt32Sat(ctx, &p, argv[0])) return JS_EXCEPTION; - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - if (p < 1 || p > 100) { - return JS_ThrowRangeError(ctx, "toPrecision() argument must be between 1 and 100"); + if (!isfinite(d)) { + to_string: + return JS_ToStringFree(ctx, __JS_NewFloat64(d)); } - return js_dtoa(ctx, d, p, JS_DTOA_PRECISION); + if (p < 1 || p > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + return js_dtoa2(ctx, d, 10, p, JS_DTOA_FORMAT_FIXED); } static const JSCFunctionListEntry js_number_proto_funcs[] = { @@ -42863,24 +42452,25 @@ static const JSCFunctionListEntry js_number_proto_funcs[] = { static JSValue js_parseInt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - const char *str; + const char *str, *p; int radix, flags; JSValue ret; - size_t len; - str = JS_ToCStringLen(ctx, &len, argv[0]); + str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; if (JS_ToInt32(ctx, &radix, argv[1])) { JS_FreeCString(ctx, str); return JS_EXCEPTION; } - flags = ATOD_TRIM_SPACES; - if (radix == 0) { - flags |= ATOD_ACCEPT_HEX_PREFIX; // Only 0x and 0X are supported - radix = 10; + if (radix != 0 && (radix < 2 || radix > 36)) { + ret = JS_NAN; + } else { + p = str; + p += skip_spaces(p); + flags = ATOD_INT_ONLY | ATOD_ACCEPT_PREFIX_AFTER_SIGN; + ret = js_atof(ctx, p, NULL, radix, flags); } - ret = js_atof(ctx, str, len, NULL, radix, flags); JS_FreeCString(ctx, str); return ret; } @@ -42888,16 +42478,15 @@ static JSValue js_parseInt(JSContext *ctx, JSValueConst this_val, static JSValue js_parseFloat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - const char *str; + const char *str, *p; JSValue ret; - int flags; - size_t len; - str = JS_ToCStringLen(ctx, &len, argv[0]); + str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; - flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY; - ret = js_atof(ctx, str, len, NULL, 10, flags); + p = str; + p += skip_spaces(p); + ret = js_atof(ctx, p, NULL, 10, 0); JS_FreeCString(ctx, str); return ret; } diff --git a/tests/test_builtin.js b/tests/test_builtin.js index 2e86cb4a2..d228ec22b 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -427,10 +427,15 @@ 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((0.5).toFixed(0), "1"); assert((-0.5).toFixed(0), "-1"); + assert((-1e-10).toFixed(0), "-0"); + + assert((1.3).toString(7), "1.2046204620462046205"); + assert((1.3).toString(35), "1.ahhhhhhhhhm"); } function test_eval2() From b24d02aef1b15d6151080dff02828420c4f74271 Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:02:18 -0400 Subject: [PATCH 2/5] Re-apply 29b45337f097772c0de307e11c25f16d822b8611 --- quickjs.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/quickjs.c b/quickjs.c index 0d691e58f..d47a849ca 100644 --- a/quickjs.c +++ b/quickjs.c @@ -12056,7 +12056,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, to_digit((uint8_t)p[1]) < radix)) { p++; } - if (!(flags & ATOD_INT_ONLY)) { + if (!(flags & ATOD_INT_ONLY) && radix == 10) { if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { is_float = true; p++; @@ -12066,9 +12066,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, (*p == sep && to_digit((uint8_t)p[1]) < radix)) p++; } - if (p > p_start && - (((*p == 'e' || *p == 'E') && radix == 10) || - ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + if (p > p_start && (*p == 'e' || *p == 'E')) { const char *p1 = p + 1; is_float = true; if (*p1 == '+') { @@ -12109,14 +12107,6 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, if (*p == 'n') { p++; atod_type = ATOD_TYPE_BIG_INT; - } else { - if (is_float && radix != 10) - goto fail; - } - } else { - if (atod_type == ATOD_TYPE_FLOAT64) { - if (is_float && radix != 10) - goto fail; } } From 10265d2f31f9dac0ab8cf96f8051440abea30b4c Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:19:50 -0400 Subject: [PATCH 3/5] Remove unnecessary includes in dtoa.c --- dtoa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dtoa.c b/dtoa.c index e145068c5..def8608f9 100644 --- a/dtoa.c +++ b/dtoa.c @@ -28,9 +28,9 @@ #include #include #include -#include +// #include #include -#include +// #include #include "cutils.h" #include "dtoa.h" From 1086fdda293938e116e0d1b57b87cf02952db3ba Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:22:44 -0400 Subject: [PATCH 4/5] Add dtoa to amalgam.js --- amalgam.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/amalgam.js b/amalgam.js index 87b0e8876..8e2f9da31 100644 --- a/amalgam.js +++ b/amalgam.js @@ -2,6 +2,8 @@ import {loadFile, writeFile} from "qjs:std" const cutils_c = loadFile("cutils.c") const cutils_h = loadFile("cutils.h") +const dtoa_c = loadFile("dtoa.c") +const dtoa_h = loadFile("dtoa.h") const libregexp_c = loadFile("libregexp.c") const libregexp_h = loadFile("libregexp.h") const libregexp_opcode_h = loadFile("libregexp-opcode.h") @@ -25,6 +27,7 @@ let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU + "#endif\n" + quickjs_c_atomics_h + cutils_h + + dtoa_h + list_h + libunicode_h // exports lre_is_id_start, used by libregexp.h + libregexp_h @@ -33,6 +36,7 @@ let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU + quickjs_h + quickjs_c + cutils_c + + dtoa_c + libregexp_c + libunicode_c + xsum_c From dff69674a46dc45b3468b3d25d229f4d773b41b3 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 31 Aug 2025 18:45:19 +0200 Subject: [PATCH 5/5] squash! fix duplicate symbols in amalgamation --- quickjs.c | 108 +++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/quickjs.c b/quickjs.c index d47a849ca..bd7d5f953 100644 --- a/quickjs.c +++ b/quickjs.c @@ -10552,7 +10552,7 @@ static int skip_spaces(const char *pc) return p - 1 - p_start; } -static inline int to_digit(int c) +static inline int js_to_digit(int c) { if (c >= '0' && c <= '9') return c - '0'; @@ -10586,7 +10586,7 @@ static inline js_limb_t js_limb_clz(js_limb_t a) return clz32(a); } -static js_limb_t mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, +static js_limb_t js_mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, js_limb_t n, js_limb_t carry) { int i; @@ -10596,7 +10596,7 @@ static js_limb_t mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *o return carry; } -static js_limb_t mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, +static js_limb_t js_mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, int n, js_limb_t carry) { int i; @@ -10615,7 +10615,7 @@ static js_limb_t mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *o } /* compute 0 - op2. carry = 0 or 1. */ -static js_limb_t mp_neg(js_limb_t *res, const js_limb_t *op2, int n) +static js_limb_t js_mp_neg(js_limb_t *res, const js_limb_t *op2, int n) { int i; js_limb_t v, carry; @@ -10630,7 +10630,7 @@ static js_limb_t mp_neg(js_limb_t *res, const js_limb_t *op2, int n) } /* tabr[] = taba[] * b + l. Return the high carry */ -static js_limb_t mp_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, +static js_limb_t js_mp_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, js_limb_t b, js_limb_t l) { js_limb_t i; @@ -10644,7 +10644,7 @@ static js_limb_t mp_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, return l; } -static js_limb_t mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, +static js_limb_t js_mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, js_limb_t b, js_limb_t r) { js_slimb_t i; @@ -10658,7 +10658,7 @@ static js_limb_t mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, } /* tabr[] += taba[] * b, return the high word. */ -static js_limb_t mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, +static js_limb_t js_mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, js_limb_t b) { js_limb_t i, l; @@ -10674,23 +10674,23 @@ static js_limb_t mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n } /* size of the result : op1_size + op2_size. */ -static void mp_mul_basecase(js_limb_t *result, +static void js_mp_mul_basecase(js_limb_t *result, const js_limb_t *op1, js_limb_t op1_size, const js_limb_t *op2, js_limb_t op2_size) { int i; js_limb_t r; - result[op1_size] = mp_mul1(result, op1, op1_size, op2[0], 0); + result[op1_size] = js_mp_mul1(result, op1, op1_size, op2[0], 0); for(i=1;i= 2^(JS_LIMB_BITS-1) */ -static inline js_limb_t udiv1norm_init(js_limb_t d) +static inline js_limb_t js_udiv1norm_init(js_limb_t d) { js_limb_t a0, a1; a1 = -d - 1; @@ -10716,8 +10716,8 @@ static inline js_limb_t udiv1norm_init(js_limb_t d) /* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 / d' with 0 <= a1 < d. */ -static inline js_limb_t udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, - js_limb_t d, js_limb_t d_inv) +static inline js_limb_t js_udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, + js_limb_t d, js_limb_t d_inv) { js_limb_t n1m, n_adj, q, r, ah; js_dlimb_t a; @@ -10739,16 +10739,16 @@ static inline js_limb_t udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, #define UDIV1NORM_THRESHOLD 3 /* b must be >= 1 << (JS_LIMB_BITS - 1) */ -static js_limb_t mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, +static js_limb_t js_mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, js_limb_t b, js_limb_t r) { js_slimb_t i; if (n >= UDIV1NORM_THRESHOLD) { js_limb_t b_inv; - b_inv = udiv1norm_init(b); + b_inv = js_udiv1norm_init(b); for(i = n - 1; i >= 0; i--) { - tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + tabr[i] = js_udiv1norm(&r, r, taba[i], b, b_inv); } } else { js_dlimb_t a1; @@ -10765,7 +10765,7 @@ static js_limb_t mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n - 1] must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' is modified and contains the remainder (nb limbs). tabq[0..na-nb] contains the quotient with tabq[na - nb] <= 1. */ -static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, +static void js_mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, const js_limb_t *tabb, js_limb_t nb) { js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; @@ -10773,13 +10773,13 @@ static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, b1 = tabb[nb - 1]; if (nb == 1) { - taba[0] = mp_div1norm(tabq, taba, na, b1, 0); + taba[0] = js_mp_div1norm(tabq, taba, na, b1, 0); return; } n = na - nb; if (n >= UDIV1NORM_THRESHOLD) - b1_inv = udiv1norm_init(b1); + b1_inv = js_udiv1norm_init(b1); else b1_inv = 0; @@ -10794,21 +10794,21 @@ static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, } tabq[n] = q; if (q) { - mp_sub(taba + n, taba + n, tabb, nb, 0); + js_mp_sub(taba + n, taba + n, tabb, nb, 0); } for(i = n - 1; i >= 0; i--) { if (unlikely(taba[i + nb] >= b1)) { q = -1; } else if (b1_inv) { - q = udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + q = js_udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); } else { js_dlimb_t al; al = ((js_dlimb_t)taba[i + nb] << JS_LIMB_BITS) | taba[i + nb - 1]; q = al / b1; r = al % b1; } - r = mp_sub_mul1(taba + i, tabb, nb, q); + r = js_mp_sub_mul1(taba + i, tabb, nb, q); v = taba[i + nb]; a = v - r; @@ -10819,7 +10819,7 @@ static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, /* negative result */ for(;;) { q--; - c = mp_add(taba + i, taba + i, tabb, nb, 0); + c = js_mp_add(taba + i, taba + i, tabb, nb, 0); /* propagate carry and test if positive result */ if (c != 0) { if (++taba[i + nb] == 0) { @@ -10833,7 +10833,7 @@ static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, } /* 1 <= shift <= JS_LIMB_BITS - 1 */ -static js_limb_t mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, +static js_limb_t js_mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, int shift) { int i; @@ -10849,7 +10849,7 @@ static js_limb_t mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, /* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). 1 <= shift <= LIMB_BITS - 1 */ -static js_limb_t mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, +static js_limb_t js_mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, int shift, js_limb_t high) { int i; @@ -11119,13 +11119,13 @@ static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, r = js_bigint_new(ctx, a->len + b->len); if (!r) return NULL; - mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); + js_mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); /* correct the result if negative operands (no overflow is possible) */ if (js_bigint_sign(a)) - mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); + js_mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); if (js_bigint_sign(b)) - mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); + js_mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); return js_bigint_normalize(ctx, r); } @@ -11152,7 +11152,7 @@ static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, if (!r) return NULL; if (a_sign) { - mp_neg(r->tab, a->tab, na); + js_mp_neg(r->tab, a->tab, na); } else { memcpy(r->tab, a->tab, na * sizeof(a->tab[0])); } @@ -11166,7 +11166,7 @@ static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, return NULL; } if (b_sign) { - mp_neg(tabb, b->tab, nb); + js_mp_neg(tabb, b->tab, nb); } else { memcpy(tabb, b->tab, nb * sizeof(tabb[0])); } @@ -11194,8 +11194,8 @@ static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, /* normalize 'b' */ shift = js_limb_clz(tabb[nb - 1]); if (shift != 0) { - mp_shl(tabb, tabb, nb, shift); - h = mp_shl(r->tab, r->tab, na, shift); + js_mp_shl(tabb, tabb, nb, shift); + h = js_mp_shl(r->tab, r->tab, na, shift); if (h != 0) r->tab[na++] = h; } @@ -11209,23 +11209,23 @@ static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, // js_bigint_dump1(ctx, "a", r->tab, na); // js_bigint_dump1(ctx, "b", tabb, nb); - mp_divnorm(q->tab, r->tab, na, tabb, nb); + js_mp_divnorm(q->tab, r->tab, na, tabb, nb); js_free(ctx, tabb); if (is_rem) { js_free(ctx, q); if (shift != 0) - mp_shr(r->tab, r->tab, nb, shift, 0); + js_mp_shr(r->tab, r->tab, nb, shift, 0); r->tab[nb++] = 0; if (a_sign) - mp_neg(r->tab, r->tab, nb); + js_mp_neg(r->tab, r->tab, nb); r = js_bigint_normalize1(ctx, r, nb); return r; } else { js_free(ctx, r); q->tab[na - nb + 1] = 0; if (a_sign ^ b_sign) { - mp_neg(q->tab, q->tab, q->len); + js_mp_neg(q->tab, q->tab, q->len); } q = js_bigint_normalize(ctx, q); return q; @@ -11321,7 +11321,7 @@ static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, r->tab[i + d] = a->tab[i]; } } else { - l = mp_shl(r->tab + d, a->tab, a->len, shift); + l = js_mp_shl(r->tab + d, a->tab, a->len, shift); if (js_bigint_sign(a)) l |= (js_limb_t)(-1) << shift; r = js_bigint_extend(ctx, r, l); @@ -11350,7 +11350,7 @@ static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, } /* no normalization is needed */ } else { - mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); + js_mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); r = js_bigint_normalize(ctx, r); } return r; @@ -11718,7 +11718,7 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx, /* XXX: slow */ v = 0; for(i = 0; i < digits_per_limb; i++) { - c = to_digit(*p); + c = js_to_digit(*p); if (c >= radix) break; p++; @@ -11729,7 +11729,7 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx, if (len == 1 && r->tab[0] == 0) { r->tab[0] = v; } else { - h = mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); + h = js_mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); if (h != 0) { r->tab[len++] = h; } @@ -11746,7 +11746,7 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx, r->len = n_limbs; memset(r->tab, 0, sizeof(r->tab[0]) * n_limbs); for(i = 0; i < n_digits; i++) { - c = to_digit(p[n_digits - 1 - i]); + c = js_to_digit(p[n_digits - 1 - i]); assert(c < radix); bit_pos = i * log2_radix; shift = bit_pos & (JS_LIMB_BITS - 1); @@ -11801,7 +11801,7 @@ static char *js_u64toa(char *q, int64_t n, unsigned int base) } /* len >= 1. 2 <= radix <= 36 */ -static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) +static char *js_limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) { int digit, i; @@ -11825,11 +11825,11 @@ static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) #define JS_RADIX_MAX 36 -static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +static const uint8_t js_digits_per_limb_table[JS_RADIX_MAX - 1] = { 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, }; -static const js_limb_t radix_base_table[JS_RADIX_MAX - 1] = { +static const js_limb_t js_radix_base_table[JS_RADIX_MAX - 1] = { 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, @@ -11893,7 +11893,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) if (!is_binary_radix) { int len; js_limb_t radix_base, v; - radix_base = radix_base_table[radix - 2]; + radix_base = js_radix_base_table[radix - 2]; len = r->len; for(;;) { /* remove leading zero limbs */ @@ -11906,8 +11906,8 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) } break; } else { - v = mp_div1(r->tab, r->tab, len, radix_base, 0); - q = limb_to_a(q, v, radix, digits_per_limb_table[radix - 2]); + v = js_mp_div1(r->tab, r->tab, len, radix_base, 0); + q = js_limb_to_a(q, v, radix, js_digits_per_limb_table[radix - 2]); } } } else { @@ -12031,7 +12031,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, goto no_prefix; } /* there must be a digit after the prefix */ - if (to_digit((uint8_t)*p) >= radix) + if (js_to_digit((uint8_t)*p) >= radix) goto fail; no_prefix: ; } else { @@ -12050,20 +12050,20 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, radix = 10; is_float = false; p_start = p; - while (to_digit((uint8_t)*p) < radix + while (js_to_digit((uint8_t)*p) < radix || (*p == sep && (radix != 10 || p != p_start + 1 || p[-1] != '0') && - to_digit((uint8_t)p[1]) < radix)) { + js_to_digit((uint8_t)p[1]) < radix)) { p++; } if (!(flags & ATOD_INT_ONLY) && radix == 10) { - if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { + if (*p == '.' && (p > p_start || js_to_digit((uint8_t)p[1]) < radix)) { is_float = true; p++; if (*p == sep) goto fail; - while (to_digit((uint8_t)*p) < radix || - (*p == sep && to_digit((uint8_t)p[1]) < radix)) + while (js_to_digit((uint8_t)*p) < radix || + (*p == sep && js_to_digit((uint8_t)p[1]) < radix)) p++; } if (p > p_start && (*p == 'e' || *p == 'E')) {