]> git.kaiwu.me - njs.git/commitdiff
Using printing and parsing library from QuickJS.
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 24 Oct 2025 03:06:42 +0000 (20:06 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Tue, 18 Nov 2025 02:56:11 +0000 (18:56 -0800)
- Number.prototype.toString(radix)
    Improved accuracy for edge cases
    Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FREE | JS_DTOA_EXP_DISABLED
- Number.prototype.toFixed(frac)
    Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FIXED
    Removed old njs_fixed_dtoa() implementation
- Number.prototype.toPrecision(prec)
    Reimplemented using njs_dtoa2() with precision format
    Removed old njs_dtoa_precision() implementation
- Number.prototype.toExponential(frac)
    Reimplemented using njs_dtoa2() with exponential format
    Removed old njs_dtoa_exponential() implementation
- parseInt()
    Simplified parsing implementation
    Removed custom njs_number_radix_parse() helper
- parseFloat()
    Simplified parsing implementation

Removed custom njs_number_bin_parse(), njs_number_oct_parse(),
njs_number_dec_parse() and njs_strtod.c module Better handling of large
numbers and denormal floats and invalid inputs.

23 files changed:
auto/clang
auto/sources
auto/types
external/njs_shell.c
src/njs.h
src/njs_clang.h
src/njs_cutils.h [new file with mode: 0644]
src/njs_diyfp.c [deleted file]
src/njs_diyfp.h [deleted file]
src/njs_dtoa.c
src/njs_dtoa.h
src/njs_dtoa_fixed.c [deleted file]
src/njs_dtoa_fixed.h [deleted file]
src/njs_json.c
src/njs_lexer.c
src/njs_main.h
src/njs_number.c
src/njs_number.h
src/njs_string.c
src/njs_strtod.c [deleted file]
src/njs_strtod.h [deleted file]
src/njs_types.h
src/test/njs_unit_test.c

index 27fe03b5a60c11d0a7d0817f4475c1e09a3f8de1..965fe7fa35c6d3ff8ba870d9082d5b99081019c0 100644 (file)
@@ -4,18 +4,6 @@
 
 # C language features.
 
-njs_feature="GCC unsigned __int128"
-njs_feature_name=NJS_HAVE_UNSIGNED_INT128
-njs_feature_run=no
-njs_feature_incs=
-njs_feature_libs=
-njs_feature_test="int main(void) {
-                      unsigned __int128 p = 0;
-                      return (int) p;
-                  }"
-. auto/feature
-
-
 njs_feature="GCC __builtin_expect()"
 njs_feature_name=NJS_HAVE_BUILTIN_EXPECT
 njs_feature_run=no
@@ -203,16 +191,3 @@ njs_feature_test="#include <sanitizer/msan_interface.h>
                       return 0;
                   }"
 . auto/feature
-
-
-njs_feature="_mm_setcsr()"
-njs_feature_name=NJS_HAVE_DENORMALS_CONTROL
-njs_feature_run=no
-njs_feature_incs=
-njs_feature_libs=
-njs_feature_test="#include <xmmintrin.h>
-                  int main(void) {
-                      _mm_setcsr(_mm_getcsr());
-                      return 0;
-                  }"
-. auto/feature
index 1a89edf5761d786b2b9beb4e6e24e485987b389a..24fd7696c84d3f0df35b44b889c9946af7b2c7de 100644 (file)
@@ -1,9 +1,6 @@
 NJS_LIB_SRCS=" \
-   src/njs_diyfp.c \
    src/njs_dtoa.c \
-   src/njs_dtoa_fixed.c \
    src/njs_str.c \
-   src/njs_strtod.c \
    src/njs_murmur_hash.c \
    src/njs_djb_hash.c \
    src/njs_utf8.c \
index 6e1c2c61af6f1ff54017f13c63ccd5da2d46ecc6..7bb9c16ad362d337d5870fb8197b66a7cfcc2d19 100644 (file)
@@ -118,57 +118,3 @@ njs_feature_test="#include <time.h>
                       return 0;
                   }"
 . auto/feature
-
-
-# Ensuring that double type is always evaluated at standard
-# precision required by njs_diyfp_t
-
-
-case $NJS_CC_NAME in
-
-    gcc)
-        NJS_CFLAGS="$NJS_CFLAGS -fexcess-precision=standard"
-    ;;
-
-    clang)
-
-        njs_found=no
-
-        njs_feature="flag -ffp-eval-method=double"
-        njs_feature_name=NJS_HAVE_FP_EVAL_METHOD
-        njs_feature_run=no
-        njs_feature_incs="-ffp-eval-method=double"
-        njs_feature_libs=
-        njs_feature_test="int main(void) {
-                             return 0;
-                         }"
-
-        . auto/feature
-
-        if [ $njs_found = yes ]; then
-            NJS_CFLAGS="$NJS_CFLAGS -ffp-eval-method=double"
-        fi
-
-    ;;
-
-    SunC)
-
-        njs_found=no
-
-        njs_feature="flag -xarch=sse2"
-        njs_feature_name=NJS_HAVE_XARCH_SSE2
-        njs_feature_run=no
-        njs_feature_incs="-xarch=sse2"
-        njs_feature_libs=
-        njs_feature_test="int main(void) {
-                             return 0;
-                         }"
-
-        . auto/feature
-
-        if [ $njs_found = yes ]; then
-            NJS_CFLAGS="$NJS_CFLAGS -xarch=sse2"
-        fi
-    ;;
-
-esac
index e459f5a653685d024213fea83b8bb7905bd9ada7..d4132d28f7c8d05a0d7444801439d75e649029a6 100644 (file)
@@ -412,8 +412,6 @@ njs_main(njs_opts_t *opts)
     njs_int_t     ret;
     njs_engine_t  *engine;
 
-    njs_mm_denormals(opts->denormals);
-
     if (opts->file == NULL) {
         if (opts->command.length != 0) {
             opts->file = (char *) "string";
@@ -637,12 +635,6 @@ njs_options_parse(njs_opts_t *opts, int argc, char **argv)
             return NJS_ERROR;
 
         case 'f':
-
-#if !(NJS_HAVE_DENORMALS_CONTROL)
-            njs_stderror("option \"-f\" is not supported\n");
-            return NJS_ERROR;
-#endif
-
             opts->denormals = 0;
             break;
 
index b4fb2617e44f85a8a3bf4feeec12722124475595..a2781a446bbe4e2fb10a1d21f6e58eb7b29cdd58 100644 (file)
--- a/src/njs.h
+++ b/src/njs.h
@@ -312,11 +312,13 @@ NJS_EXPORT njs_vm_t *njs_vm_create(njs_vm_opt_t *options);
 NJS_EXPORT void njs_vm_destroy(njs_vm_t *vm);
 NJS_EXPORT njs_int_t njs_vm_call_exit_hook(njs_vm_t *vm);
 
+/* Input must be null-terminated. */
 NJS_EXPORT njs_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end);
 NJS_EXPORT void njs_vm_set_module_loader(njs_vm_t *vm,
     njs_module_loader_t module_loader, void *opaque);
 NJS_EXPORT njs_mod_t *njs_vm_add_module(njs_vm_t *vm, njs_str_t *name,
     njs_value_t *value);
+/* Input must be null-terminated. */
 NJS_EXPORT njs_mod_t *njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name,
     u_char **start, u_char *end);
 NJS_EXPORT njs_int_t njs_vm_reuse(njs_vm_t *vm);
index af9f3350bcce3021ad4428303408c023d6899e35..3eadd875d8be9a92777f6c6fbe5b6363aaf39d1c 100644 (file)
@@ -209,24 +209,6 @@ njs_unsafe_cast_double_to_int64(double num)
 }
 
 
-#if (NJS_HAVE_DENORMALS_CONTROL)
-#include <xmmintrin.h>
-
-/*
- * 0x8000 Flush to zero
- * 0x0040 Denormals are zeros
- */
-
-#define NJS_MM_DENORMALS_MASK 0x8040
-
-#define njs_mm_denormals(on)                                                  \
-    _mm_setcsr((_mm_getcsr() & ~NJS_MM_DENORMALS_MASK) | (!(on) ? 0x8040: 0x0))
-#else
-
-#define njs_mm_denormals(on)
-#endif
-
-
 #ifndef NJS_MAX_ALIGNMENT
 
 #if (NJS_SOLARIS)
diff --git a/src/njs_cutils.h b/src/njs_cutils.h
new file mode 100644 (file)
index 0000000..4f14052
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Selected C utilities adapted from QuickJS cutils.h
+ *
+ * Copyright (c) 2024 Fabrice Bellard
+ */
+
+#ifndef _NJS_CUTILS_H_INCLUDED_
+#define _NJS_CUTILS_H_INCLUDED_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef likely
+#define likely(x)       __builtin_expect(!!(x), 1)
+#endif
+
+#ifndef unlikely
+#define unlikely(x)     __builtin_expect(!!(x), 0)
+#endif
+
+#ifndef no_inline
+#define no_inline __attribute__((noinline))
+#endif
+
+/* compatibility attribute used in dtoa implementation */
+#ifndef __maybe_unused
+#define __maybe_unused __attribute__((unused))
+#endif
+
+typedef int BOOL;
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+static inline int
+max_int(int a, int b)
+{
+    return (a > b) ? a : b;
+}
+
+static inline int
+min_int(int a, int b)
+{
+    return (a < b) ? a : b;
+}
+
+static inline uint32_t
+max_uint32(uint32_t a, uint32_t b)
+{
+    return (a > b) ? a : b;
+}
+
+static inline uint32_t
+min_uint32(uint32_t a, uint32_t b)
+{
+    return (a < b) ? a : b;
+}
+
+static inline int64_t
+max_int64(int64_t a, int64_t b)
+{
+    return (a > b) ? a : b;
+}
+
+static inline int64_t
+min_int64(int64_t a, int64_t b)
+{
+    return (a < b) ? a : b;
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int
+clz32(unsigned int a)
+{
+    return __builtin_clz(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int
+clz64(uint64_t a)
+{
+    return __builtin_clzll(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int
+ctz32(unsigned int a)
+{
+    return __builtin_ctz(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int
+ctz64(uint64_t a)
+{
+    return __builtin_ctzll(a);
+}
+
+static inline uint64_t
+float64_as_uint64(double d)
+{
+    union {
+        double   d;
+        uint64_t u64;
+    } u;
+
+    u.d = d;
+    return u.u64;
+}
+
+static inline double
+uint64_as_float64(uint64_t u64)
+{
+    union {
+        double   d;
+        uint64_t u64;
+    } u;
+
+    u.u64 = u64;
+    return u.d;
+}
+
+static inline int
+strstart(const char *str, const char *val, const char **ptr)
+{
+    const char *p = str;
+    const char *q = val;
+
+    while (*q != '\0') {
+        if (*p != *q) {
+            return 0;
+        }
+        p++;
+        q++;
+    }
+
+    if (ptr != NULL) {
+        *ptr = p;
+    }
+
+    return 1;
+}
+
+#endif /* _NJS_CUTILS_H_INCLUDED_ */
diff --git a/src/njs_diyfp.c b/src/njs_diyfp.c
deleted file mode 100644 (file)
index 37ed690..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-
-/*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) NGINX, Inc.
- *
- * An internal diy_fp implementation.
- * For details, see Loitsch, Florian. "Printing floating-point numbers quickly
- * and accurately with integers." ACM Sigplan Notices 45.6 (2010): 233-243.
- */
-
-
-#include <njs_main.h>
-#include <njs_diyfp.h>
-
-
-typedef struct njs_cpe_s {
-  uint64_t  significand;
-  int16_t   bin_exp;
-  int16_t   dec_exp;
-} njs_cpe_t;
-
-
-static const njs_cpe_t njs_cached_powers[] = {
-  { njs_uint64(0xfa8fd5a0, 0x081c0288), -1220, -348 },
-  { njs_uint64(0xbaaee17f, 0xa23ebf76), -1193, -340 },
-  { njs_uint64(0x8b16fb20, 0x3055ac76), -1166, -332 },
-  { njs_uint64(0xcf42894a, 0x5dce35ea), -1140, -324 },
-  { njs_uint64(0x9a6bb0aa, 0x55653b2d), -1113, -316 },
-  { njs_uint64(0xe61acf03, 0x3d1a45df), -1087, -308 },
-  { njs_uint64(0xab70fe17, 0xc79ac6ca), -1060, -300 },
-  { njs_uint64(0xff77b1fc, 0xbebcdc4f), -1034, -292 },
-  { njs_uint64(0xbe5691ef, 0x416bd60c), -1007, -284 },
-  { njs_uint64(0x8dd01fad, 0x907ffc3c),  -980, -276 },
-  { njs_uint64(0xd3515c28, 0x31559a83),  -954, -268 },
-  { njs_uint64(0x9d71ac8f, 0xada6c9b5),  -927, -260 },
-  { njs_uint64(0xea9c2277, 0x23ee8bcb),  -901, -252 },
-  { njs_uint64(0xaecc4991, 0x4078536d),  -874, -244 },
-  { njs_uint64(0x823c1279, 0x5db6ce57),  -847, -236 },
-  { njs_uint64(0xc2109436, 0x4dfb5637),  -821, -228 },
-  { njs_uint64(0x9096ea6f, 0x3848984f),  -794, -220 },
-  { njs_uint64(0xd77485cb, 0x25823ac7),  -768, -212 },
-  { njs_uint64(0xa086cfcd, 0x97bf97f4),  -741, -204 },
-  { njs_uint64(0xef340a98, 0x172aace5),  -715, -196 },
-  { njs_uint64(0xb23867fb, 0x2a35b28e),  -688, -188 },
-  { njs_uint64(0x84c8d4df, 0xd2c63f3b),  -661, -180 },
-  { njs_uint64(0xc5dd4427, 0x1ad3cdba),  -635, -172 },
-  { njs_uint64(0x936b9fce, 0xbb25c996),  -608, -164 },
-  { njs_uint64(0xdbac6c24, 0x7d62a584),  -582, -156 },
-  { njs_uint64(0xa3ab6658, 0x0d5fdaf6),  -555, -148 },
-  { njs_uint64(0xf3e2f893, 0xdec3f126),  -529, -140 },
-  { njs_uint64(0xb5b5ada8, 0xaaff80b8),  -502, -132 },
-  { njs_uint64(0x87625f05, 0x6c7c4a8b),  -475, -124 },
-  { njs_uint64(0xc9bcff60, 0x34c13053),  -449, -116 },
-  { njs_uint64(0x964e858c, 0x91ba2655),  -422, -108 },
-  { njs_uint64(0xdff97724, 0x70297ebd),  -396, -100 },
-  { njs_uint64(0xa6dfbd9f, 0xb8e5b88f),  -369,  -92 },
-  { njs_uint64(0xf8a95fcf, 0x88747d94),  -343,  -84 },
-  { njs_uint64(0xb9447093, 0x8fa89bcf),  -316,  -76 },
-  { njs_uint64(0x8a08f0f8, 0xbf0f156b),  -289,  -68 },
-  { njs_uint64(0xcdb02555, 0x653131b6),  -263,  -60 },
-  { njs_uint64(0x993fe2c6, 0xd07b7fac),  -236,  -52 },
-  { njs_uint64(0xe45c10c4, 0x2a2b3b06),  -210,  -44 },
-  { njs_uint64(0xaa242499, 0x697392d3),  -183,  -36 },
-  { njs_uint64(0xfd87b5f2, 0x8300ca0e),  -157,  -28 },
-  { njs_uint64(0xbce50864, 0x92111aeb),  -130,  -20 },
-  { njs_uint64(0x8cbccc09, 0x6f5088cc),  -103,  -12 },
-  { njs_uint64(0xd1b71758, 0xe219652c),   -77,   -4 },
-  { njs_uint64(0x9c400000, 0x00000000),   -50,    4 },
-  { njs_uint64(0xe8d4a510, 0x00000000),   -24,   12 },
-  { njs_uint64(0xad78ebc5, 0xac620000),     3,   20 },
-  { njs_uint64(0x813f3978, 0xf8940984),    30,   28 },
-  { njs_uint64(0xc097ce7b, 0xc90715b3),    56,   36 },
-  { njs_uint64(0x8f7e32ce, 0x7bea5c70),    83,   44 },
-  { njs_uint64(0xd5d238a4, 0xabe98068),   109,   52 },
-  { njs_uint64(0x9f4f2726, 0x179a2245),   136,   60 },
-  { njs_uint64(0xed63a231, 0xd4c4fb27),   162,   68 },
-  { njs_uint64(0xb0de6538, 0x8cc8ada8),   189,   76 },
-  { njs_uint64(0x83c7088e, 0x1aab65db),   216,   84 },
-  { njs_uint64(0xc45d1df9, 0x42711d9a),   242,   92 },
-  { njs_uint64(0x924d692c, 0xa61be758),   269,  100 },
-  { njs_uint64(0xda01ee64, 0x1a708dea),   295,  108 },
-  { njs_uint64(0xa26da399, 0x9aef774a),   322,  116 },
-  { njs_uint64(0xf209787b, 0xb47d6b85),   348,  124 },
-  { njs_uint64(0xb454e4a1, 0x79dd1877),   375,  132 },
-  { njs_uint64(0x865b8692, 0x5b9bc5c2),   402,  140 },
-  { njs_uint64(0xc83553c5, 0xc8965d3d),   428,  148 },
-  { njs_uint64(0x952ab45c, 0xfa97a0b3),   455,  156 },
-  { njs_uint64(0xde469fbd, 0x99a05fe3),   481,  164 },
-  { njs_uint64(0xa59bc234, 0xdb398c25),   508,  172 },
-  { njs_uint64(0xf6c69a72, 0xa3989f5c),   534,  180 },
-  { njs_uint64(0xb7dcbf53, 0x54e9bece),   561,  188 },
-  { njs_uint64(0x88fcf317, 0xf22241e2),   588,  196 },
-  { njs_uint64(0xcc20ce9b, 0xd35c78a5),   614,  204 },
-  { njs_uint64(0x98165af3, 0x7b2153df),   641,  212 },
-  { njs_uint64(0xe2a0b5dc, 0x971f303a),   667,  220 },
-  { njs_uint64(0xa8d9d153, 0x5ce3b396),   694,  228 },
-  { njs_uint64(0xfb9b7cd9, 0xa4a7443c),   720,  236 },
-  { njs_uint64(0xbb764c4c, 0xa7a44410),   747,  244 },
-  { njs_uint64(0x8bab8eef, 0xb6409c1a),   774,  252 },
-  { njs_uint64(0xd01fef10, 0xa657842c),   800,  260 },
-  { njs_uint64(0x9b10a4e5, 0xe9913129),   827,  268 },
-  { njs_uint64(0xe7109bfb, 0xa19c0c9d),   853,  276 },
-  { njs_uint64(0xac2820d9, 0x623bf429),   880,  284 },
-  { njs_uint64(0x80444b5e, 0x7aa7cf85),   907,  292 },
-  { njs_uint64(0xbf21e440, 0x03acdd2d),   933,  300 },
-  { njs_uint64(0x8e679c2f, 0x5e44ff8f),   960,  308 },
-  { njs_uint64(0xd433179d, 0x9c8cb841),   986,  316 },
-  { njs_uint64(0x9e19db92, 0xb4e31ba9),  1013,  324 },
-  { njs_uint64(0xeb96bf6e, 0xbadf77d9),  1039,  332 },
-  { njs_uint64(0xaf87023b, 0x9bf0ee6b),  1066,  340 },
-};
-
-
-#define NJS_D_1_LOG2_10     0.30102999566398114 /* 1 / log2(10). */
-
-
-njs_diyfp_t
-njs_cached_power_dec(int exp, int *dec_exp)
-{
-    u_int            index;
-    const njs_cpe_t  *cp;
-
-    index = (exp + NJS_DECIMAL_EXPONENT_OFF) / NJS_DECIMAL_EXPONENT_DIST;
-    cp = &njs_cached_powers[index];
-
-    *dec_exp = cp->dec_exp;
-
-    return njs_diyfp(cp->significand, cp->bin_exp);
-}
-
-
-njs_diyfp_t
-njs_cached_power_bin(int exp, int *dec_exp)
-{
-    int              k;
-    u_int            index;
-    const njs_cpe_t  *cp;
-
-    k = (int) ceil((-61 - exp) * NJS_D_1_LOG2_10)
-        + NJS_DECIMAL_EXPONENT_OFF - 1;
-
-    index = (unsigned) (k >> 3) + 1;
-
-    cp = &njs_cached_powers[index];
-
-    *dec_exp = -(NJS_DECIMAL_EXPONENT_MIN + (int) (index << 3));
-
-    return njs_diyfp(cp->significand, cp->bin_exp);
-}
diff --git a/src/njs_diyfp.h b/src/njs_diyfp.h
deleted file mode 100644 (file)
index cfc3423..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-
-/*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) NGINX, Inc.
- *
- * An internal diy_fp implementation.
- * For details, see Loitsch, Florian. "Printing floating-point numbers quickly
- * and accurately with integers." ACM Sigplan Notices 45.6 (2010): 233-243.
- */
-
-#ifndef _NJS_DIYFP_H_INCLUDED_
-#define _NJS_DIYFP_H_INCLUDED_
-
-
-typedef struct {
-    uint64_t    significand;
-    int         exp;
-} njs_diyfp_t;
-
-
-typedef union {
-    double      d;
-    uint64_t    u64;
-} njs_diyfp_conv_t;
-
-
-#define njs_diyfp(_s, _e)           (njs_diyfp_t) \
-                                    { .significand = (_s), .exp = (_e) }
-#define njs_uint64(h, l)            (((uint64_t) (h) << 32) + (l))
-
-
-#define NJS_DBL_SIGNIFICAND_SIZE    52
-#define NJS_DBL_EXPONENT_OFFSET     ((int64_t) 0x3ff)
-#define NJS_DBL_EXPONENT_BIAS       (NJS_DBL_EXPONENT_OFFSET                  \
-                                     + NJS_DBL_SIGNIFICAND_SIZE)
-#define NJS_DBL_EXPONENT_MIN        (-NJS_DBL_EXPONENT_BIAS)
-#define NJS_DBL_EXPONENT_MAX        (0x7ff - NJS_DBL_EXPONENT_BIAS)
-#define NJS_DBL_EXPONENT_DENORMAL   (-NJS_DBL_EXPONENT_BIAS + 1)
-
-#define NJS_DBL_SIGNIFICAND_MASK    njs_uint64(0x000FFFFF, 0xFFFFFFFF)
-#define NJS_DBL_HIDDEN_BIT          njs_uint64(0x00100000, 0x00000000)
-#define NJS_DBL_EXPONENT_MASK       njs_uint64(0x7FF00000, 0x00000000)
-#define NJS_DBL_SIGN_MASK           njs_uint64(0x80000000, 0x00000000)
-
-#define NJS_DIYFP_SIGNIFICAND_SIZE  64
-
-#define NJS_SIGNIFICAND_SIZE        53
-#define NJS_SIGNIFICAND_SHIFT       (NJS_DIYFP_SIGNIFICAND_SIZE               \
-                                     - NJS_DBL_SIGNIFICAND_SIZE)
-
-#define NJS_DECIMAL_EXPONENT_OFF    348
-#define NJS_DECIMAL_EXPONENT_MIN    (-348)
-#define NJS_DECIMAL_EXPONENT_MAX    340
-#define NJS_DECIMAL_EXPONENT_DIST   8
-
-
-njs_inline njs_diyfp_t
-njs_d2diyfp(double d)
-{
-    int           biased_exp;
-    uint64_t      significand;
-    njs_diyfp_t   r;
-
-    union {
-        double    d;
-        uint64_t  u64;
-    } u;
-
-    u.d = d;
-
-    biased_exp = (u.u64 & NJS_DBL_EXPONENT_MASK) >> NJS_DBL_SIGNIFICAND_SIZE;
-    significand = u.u64 & NJS_DBL_SIGNIFICAND_MASK;
-
-    if (biased_exp != 0) {
-        r.significand = significand + NJS_DBL_HIDDEN_BIT;
-        r.exp = biased_exp - NJS_DBL_EXPONENT_BIAS;
-
-    } else {
-        r.significand = significand;
-        r.exp = NJS_DBL_EXPONENT_MIN + 1;
-    }
-
-    return r;
-}
-
-
-njs_inline double
-njs_diyfp2d(njs_diyfp_t v)
-{
-    int               exp;
-    uint64_t          significand, biased_exp;
-    njs_diyfp_conv_t  conv;
-
-    exp = v.exp;
-    significand = v.significand;
-
-    while (significand > NJS_DBL_HIDDEN_BIT + NJS_DBL_SIGNIFICAND_MASK) {
-        significand >>= 1;
-        exp++;
-    }
-
-    if (exp >= NJS_DBL_EXPONENT_MAX) {
-        return INFINITY;
-    }
-
-    if (exp < NJS_DBL_EXPONENT_DENORMAL) {
-        return 0.0;
-    }
-
-    while (exp > NJS_DBL_EXPONENT_DENORMAL
-           && (significand & NJS_DBL_HIDDEN_BIT) == 0)
-    {
-        significand <<= 1;
-        exp--;
-    }
-
-    if (exp == NJS_DBL_EXPONENT_DENORMAL
-        && (significand & NJS_DBL_HIDDEN_BIT) == 0)
-    {
-        biased_exp = 0;
-
-    } else {
-        biased_exp = (uint64_t) (exp + NJS_DBL_EXPONENT_BIAS);
-    }
-
-    conv.u64 = (significand & NJS_DBL_SIGNIFICAND_MASK)
-                | (biased_exp << NJS_DBL_SIGNIFICAND_SIZE);
-
-    return conv.d;
-}
-
-
-njs_inline njs_diyfp_t
-njs_diyfp_shift_left(njs_diyfp_t v, unsigned shift)
-{
-    return njs_diyfp(v.significand << shift, v.exp - shift);
-}
-
-
-njs_inline njs_diyfp_t
-njs_diyfp_shift_right(njs_diyfp_t v, unsigned shift)
-{
-    return njs_diyfp(v.significand >> shift, v.exp + shift);
-}
-
-
-njs_inline njs_diyfp_t
-njs_diyfp_sub(njs_diyfp_t lhs, njs_diyfp_t rhs)
-{
-    return njs_diyfp(lhs.significand - rhs.significand, lhs.exp);
-}
-
-
-njs_inline njs_diyfp_t
-njs_diyfp_mul(njs_diyfp_t lhs, njs_diyfp_t rhs)
-{
-#if (NJS_HAVE_UNSIGNED_INT128)
-
-    uint64_t       l, h;
-    njs_uint128_t  u128;
-
-    u128 = (njs_uint128_t) (lhs.significand)
-           * (njs_uint128_t) (rhs.significand);
-
-    h = u128 >> 64;
-    l = (uint64_t) u128;
-
-    /* rounding. */
-
-    if (l & ((uint64_t) 1 << 63)) {
-        h++;
-    }
-
-    return njs_diyfp(h, lhs.exp + rhs.exp + 64);
-
-#else
-
-    uint64_t  a, b, c, d, ac, bc, ad, bd, tmp;
-
-    a = lhs.significand >> 32;
-    b = lhs.significand & 0xffffffff;
-    c = rhs.significand >> 32;
-    d = rhs.significand & 0xffffffff;
-
-    ac = a * c;
-    bc = b * c;
-    ad = a * d;
-    bd = b * d;
-
-    tmp = (bd >> 32) + (ad & 0xffffffff) + (bc & 0xffffffff);
-
-    /* mult_round. */
-
-    tmp += 1U << 31;
-
-    return njs_diyfp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32),
-                     lhs.exp + rhs.exp + 64);
-
-#endif
-}
-
-
-njs_inline njs_diyfp_t
-njs_diyfp_normalize(njs_diyfp_t v)
-{
-    return njs_diyfp_shift_left(v, njs_leading_zeros64(v.significand));
-}
-
-
-njs_diyfp_t njs_cached_power_dec(int exp, int *dec_exp);
-njs_diyfp_t njs_cached_power_bin(int exp, int *dec_exp);
-
-
-#endif /* _NJS_DIYFP_H_INCLUDED_ */
index 7d8e9e4441cd76241b9e30abf8ac23f9f32af23b..7f0244000a46e7bb8c576500b34c3082ab629c35 100644 (file)
@@ -1,18 +1,7 @@
-
 /*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) NGINX, Inc.
- *
- * Grisu2 algorithm implementation for printing floating-point numbers based
- * upon the work of Milo Yip and Doug Currie.
- *
- * For algorithm information, see Loitsch, Florian. "Printing
- * floating-point numbers quickly and accurately with integers." ACM Sigplan
- * Notices 45.6 (2010): 233-243.
+ * Tiny float64 printing and parsing library
  *
- * Copyright (C) 2015 Doug Currie
- * based on dtoa_milo.h
- * Copyright (C) 2014 Milo Yip
+ * 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
  *
  * 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
+ * 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.
+ *
+ * bellard/quickjs/commit/dbbca3dbf3856938120071225a5e4c906d3177e8
  */
 
-
 #include <njs_main.h>
-#include <njs_diyfp.h>
-#include <njs_dtoa.h>
+#include <njs_cutils.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 ?)
+*/
 
-njs_inline void
-njs_round(char *start, size_t length, uint64_t delta, uint64_t rest,
-    uint64_t ten_kappa, uint64_t margin)
-{
-    while (rest < margin && delta - rest >= ten_kappa
-           && (rest + ten_kappa < margin ||  /* closer */
-               margin - rest > rest + ten_kappa - margin))
-    {
-        start[length - 1]--;
-        rest += ten_kappa;
-    }
-}
+#define USE_POW5_TABLE
+/* use fast path to print small integers in free format */
+#define USE_FAST_INT
 
+#define LIMB_LOG2_BITS 5
 
-njs_inline void
-njs_round_prec(char *start, size_t length, uint64_t rest, uint64_t ten_kappa,
-    uint64_t unit, int *kappa)
-{
-    njs_int_t  i;
+#define LIMB_BITS (1 << LIMB_LOG2_BITS)
 
-    if (unit >= ten_kappa || ten_kappa - unit <= unit) {
-        return;
-    }
+typedef int32_t slimb_t;
+typedef uint32_t limb_t;
+typedef uint64_t dlimb_t;
 
-    if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit)) {
-        return;
-    }
+#define LIMB_DIGITS 9
 
-    if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit))) {
-        start[length - 1]++;
+#define JS_RADIX_MAX 36
 
-        for (i = length - 1; i > 0; --i) {
-            if (start[i] != '0' + 10) {
-                break;
-            }
+#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */
+#define MANT_LEN_MAX 18 /* < 36^100 */
 
-            start[i] = '0';
-            start[i - 1]++;
-        }
+typedef intptr_t mp_size_t;
 
-        if (start[0] == '0' + 10) {
-            start[0] = '1';
-            *kappa += 1;
-        }
+/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */
+typedef struct {
+    int len; /* >= 1 */
+    limb_t tab[];
+} mpb_t;
+
+static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n)
+{
+    size_t i;
+    limb_t k, a;
+
+    k=b;
+    for(i=0;i<n;i++) {
+        if (k == 0)
+            break;
+        a = tab[i] + k;
+        k = (a < k);
+        tab[i] = a;
     }
+    return k;
 }
 
-
-njs_inline int
-njs_dec_count(uint32_t n)
+/* tabr[] = taba[] * b + l. Return the high carry */
+static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, 
+                      limb_t b, limb_t l)
 {
-    if (n < 10000) {
-        if (n < 100) {
-            return (n < 10) ? 1 : 2;
+    limb_t i;
+    dlimb_t t;
 
-        } else {
-            return (n < 1000) ? 3 : 4;
-        }
-
-    } else {
-        if (n < 1000000) {
-            return (n < 100000) ? 5 : 6;
-
-        } else {
-            if (n < 100000000) {
-                return (n < 10000000) ? 7 : 8;
-
-            } else {
-                return (n < 1000000000) ? 9 : 10;
-            }
-        }
+    for(i = 0; i < n; i++) {
+        t = (dlimb_t)taba[i] * (dlimb_t)b + l;
+        tabr[i] = t;
+        l = t >> LIMB_BITS;
     }
+    return l;
 }
 
+/* WARNING: d must be >= 2^(LIMB_BITS-1) */
+static inline limb_t udiv1norm_init(limb_t d)
+{
+    limb_t a0, a1;
+    a1 = -d - 1;
+    a0 = -1;
+    return (((dlimb_t)a1 << LIMB_BITS) | a0) / d;
+}
 
-njs_inline size_t
-njs_digit_gen(njs_diyfp_t v, njs_diyfp_t high, uint64_t delta, char *start,
-    int *dec_exp)
+/* 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)
 {
-    int          kappa;
-    char         *p;
-    uint32_t     integer, d;
-    uint64_t     fraction, rest, margin;
-    njs_diyfp_t  one;
+    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 const uint64_t pow10[] = {
-        1,
-        10,
-        100,
-        1000,
-        10000,
-        100000,
-        1000000,
-        10000000,
-        100000000,
-        1000000000
-    };
+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;
+}
 
-    one = njs_diyfp((uint64_t) 1 << -high.exp, high.exp);
-    integer = (uint32_t) (high.significand >> -one.exp);
-    fraction = high.significand & (one.significand - 1);
+/* 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;
+
+    njs_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);
+}
 
-    margin = njs_diyfp_sub(high, v).significand;
+/* 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;
+
+    njs_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;
+}
 
-    p = start;
+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;
 
-    kappa = njs_dec_count(integer);
+    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;
+}
 
-    while (kappa > 0) {
+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");
+}
 
-        switch (kappa) {
-        case 10: d = integer / 1000000000; integer %= 1000000000; break;
-        case  9: d = integer /  100000000; integer %=  100000000; break;
-        case  8: d = integer /   10000000; integer %=   10000000; break;
-        case  7: d = integer /    1000000; integer %=    1000000; break;
-        case  6: d = integer /     100000; integer %=     100000; break;
-        case  5: d = integer /      10000; integer %=      10000; break;
-        case  4: d = integer /       1000; integer %=       1000; break;
-        case  3: d = integer /        100; integer %=        100; break;
-        case  2: d = integer /         10; integer %=         10; break;
-        default: d = integer;              integer =           0; break;
-        }
+static void mpb_renorm(mpb_t *r)
+{
+    while (r->len > 1 && r->tab[r->len - 1] == 0)
+        r->len--;
+}
 
-        if (d != 0 || p != start) {
-            *p++ = '0' + d;
+#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;
+}
 
-        kappa--;
-
-        rest = ((uint64_t) integer << -one.exp) + fraction;
-
-        if (rest < delta) {
-            *dec_exp += kappa;
-            njs_round(start, p - start, delta, rest, pow10[kappa] << -one.exp,
-                      margin);
-            return p - start;
-        }
+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;
+}
 
-    /* kappa = 0. */
+enum {
+    JS_RNDN, /* round to nearest, ties to even */
+    JS_RNDNA, /* round to nearest, ties away from zero */
+    JS_RNDZ,
+};
 
-    for ( ;; ) {
-        fraction *= 10;
-        delta *= 10;
+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;
+}
 
-        d = (uint32_t) (fraction >> -one.exp);
+/* 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 (d != 0 || p != start) {
-            *p++ = '0' + d;
+    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;
         }
 
-        fraction &= one.significand - 1;
-        kappa--;
-
-        if (fraction < delta) {
-            *dec_exp += kappa;
-            margin *= (-kappa < 10) ? pow10[-kappa] : 0;
-            njs_round(start, p - start, delta, fraction, one.significand,
-                      margin);
-            return p - start;
+        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;
+            }
         }
     }
 }
 
-
-njs_inline size_t
-njs_digit_gen_prec(njs_diyfp_t v, size_t prec, char *start, int *dec_exp)
+/* return -1, 0 or 1 */
+static int mpb_cmp(const mpb_t *a, const mpb_t *b)
 {
-    int          kappa;
-    char         *p;
-    uint32_t     integer, divisor;
-    uint64_t     fraction, rest, error;
-    njs_diyfp_t  one;
-
-    static const uint64_t pow10[] = {
-        1,
-        10,
-        100,
-        1000,
-        10000,
-        100000,
-        1000000,
-        10000000,
-        100000000,
-        1000000000
-    };
-
-    one = njs_diyfp((uint64_t) 1 << -v.exp, v.exp);
-    integer = (uint32_t) (v.significand >> -one.exp);
-    fraction = v.significand & (one.significand - 1);
-
-    error = 1;
-
-    p = start;
-
-    kappa = njs_dec_count(integer);
-
-    while (kappa > 0) {
-        divisor = pow10[kappa - 1];
-
-        *p++ = '0' + integer / divisor;
-
-        integer %= divisor;
-
-        kappa--;
-        prec--;
-
-        if (prec == 0) {
-            rest = ((uint64_t) integer << -one.exp) + fraction;
-            njs_round_prec(start, p - start, rest, pow10[kappa] << -one.exp,
-                           error, &kappa);
-
-            *dec_exp += kappa;
-            return p - start;
+    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;
         }
     }
-
-    /* kappa = 0. */
-
-    while (prec > 0 && fraction > error) {
-        fraction *= 10;
-        error *= 10;
-
-        *p++ = '0' + (fraction >> -one.exp);
-
-        fraction &= one.significand - 1;
-        kappa--;
-        prec--;
-    }
-
-    njs_round_prec(start, p - start, fraction, one.significand, error, &kappa);
-
-    *dec_exp += kappa;
-
-    return p - start;
+    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
+}
 
-njs_inline njs_diyfp_t
-njs_diyfp_normalize_boundary(njs_diyfp_t v)
+static uint64_t mpb_get_u64(mpb_t *r)
 {
-    while ((v.significand & (NJS_DBL_HIDDEN_BIT << 1)) == 0) {
-        v.significand <<= 1;
-        v.exp--;
+#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);
     }
-
-    return njs_diyfp_shift_left(v, NJS_SIGNIFICAND_SHIFT - 2);
+#endif
 }
 
-
-njs_inline void
-njs_diyfp_normalize_boundaries(njs_diyfp_t v, njs_diyfp_t* minus,
-    njs_diyfp_t* plus)
+/* floor_log2() = position of the first non zero bit or -1 if zero. */
+static int mpb_floor_log2(mpb_t *a)
 {
-    njs_diyfp_t  pl, mi;
-
-    pl = njs_diyfp_normalize_boundary(njs_diyfp((v.significand << 1) + 1,
-                                                v.exp - 1));
-
-    if (v.significand == NJS_DBL_HIDDEN_BIT) {
-        mi = njs_diyfp((v.significand << 2) - 1, v.exp - 2);
+    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 {
-        mi = njs_diyfp((v.significand << 1) - 1, v.exp - 1);
+        mult = mul_log2_radix_table[radix - 2];
+        return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2;
     }
-
-    mi.significand <<= mi.exp - pl.exp;
-    mi.exp = pl.exp;
-
-    *plus = pl;
-    *minus = mi;
 }
 
-
-/*
- * Grisu2 produces optimal (shortest) decimal representation for 99.8%
- * of IEEE doubles. For remaining 0.2% bignum algorithm like Dragon4 is requred.
- */
-njs_inline size_t
-njs_grisu2(double value, char *start, int *point)
+#if 0
+static void build_mul_log2_radix_table(void)
 {
-    int          dec_exp;
-    size_t       length;
-    njs_diyfp_t  v, low, high, ten_mk, scaled_v, scaled_low, scaled_high;
-
-    v = njs_d2diyfp(value);
-
-    njs_diyfp_normalize_boundaries(v, &low, &high);
-
-    ten_mk = njs_cached_power_bin(high.exp, &dec_exp);
-
-    scaled_v = njs_diyfp_mul(njs_diyfp_normalize(v), ten_mk);
-    scaled_low = njs_diyfp_mul(low, ten_mk);
-    scaled_high = njs_diyfp_mul(high, ten_mk);
-
-    scaled_low.significand++;
-    scaled_high.significand--;
-
-    length = njs_digit_gen(scaled_v, scaled_high,
-                         scaled_high.significand - scaled_low.significand,
-                         start, &dec_exp);
-
-    *point = length + dec_exp;
-
-    return length;
+    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");
 }
 
-
-njs_inline size_t
-njs_grisu2_prec(double value, char *start, size_t prec, int *point)
+static void mul_log2_radix_test(void)
 {
-    int          dec_exp;
-    size_t       length;
-    njs_diyfp_t  v, ten_mk, scaled_v;
-
-    v = njs_diyfp_normalize(njs_d2diyfp(value));
-
-    ten_mk = njs_cached_power_bin(v.exp, &dec_exp);
-
-    scaled_v = njs_diyfp_mul(v, ten_mk);
-
-    length = njs_digit_gen_prec(scaled_v, prec, start, &dec_exp);
-
-    *point = length + dec_exp;
-
-    return length;
+    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
 
-
-njs_inline size_t
-njs_write_exponent(int exp, char *start)
+static void u32toa_len(char *buf, uint32_t n, size_t len)
 {
-    char      *p;
-    size_t    length;
-    uint32_t  u32;
-    char      buf[4];
-
-    /* -324 <= exp <= 308. */
+    int digit, i;
+    for(i = len - 1; i >= 0; i--) {
+        digit = n % 10;
+        n = n / 10;
+        buf[i] = digit + '0';
+    }
+}
 
-    if (exp < 0) {
-        *start++ = '-';
-        exp = -exp;
+/* 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 {
-        *start++ = '+';
+        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;
+        }
     }
+}
 
-    u32 = exp;
-    p = buf + njs_length(buf);
-
+size_t njs_u32toa(char *buf, uint32_t n)
+{
+    char buf1[10], *q;
+    size_t len;
+    
+    q = buf1 + sizeof(buf1);
     do {
-        *--p = u32 % 10 + '0';
-        u32 /= 10;
-    } while (u32 != 0);
-
-    length = buf + njs_length(buf) - p;
-
-    memcpy(start, p, length);
-
-    return length + 1;
+        *--q = n % 10 + '0';
+        n /= 10;
+    } while (n != 0);
+    len = buf1 + sizeof(buf1) - q;
+    memcpy(buf, q, len);
+    return len;
 }
 
-
-njs_inline size_t
-njs_dtoa_format(char *start, size_t len, int point)
+size_t njs_i32toa(char *buf, int32_t n)
 {
-    int     offset, length;
-    size_t  size;
-
-    length = (int) len;
-
-    if (length <= point && point <= 21) {
-
-        /* 1234e7 -> 12340000000 */
+    if (n >= 0) {
+        return njs_u32toa(buf, n);
+    } else {
+        buf[0] = '-';
+        return njs_u32toa(buf + 1, -(uint32_t)n) + 1;
+    }
+}
 
-        if (point - length > 0) {
-            njs_memset(&start[length], '0', point - length);
+#ifdef USE_FAST_INT
+size_t njs_u64toa(char *buf, uint64_t n)
+{
+    if (n < 0x100000000) {
+        return njs_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 += njs_u32toa(q, n1);
         }
-
-        return point;
+        u32toa_len(q, n, 9);
+        q += 9;
+        return q - buf;
     }
+}
 
-    if (0 < point && point <= 21) {
-
-        /* 1234e-2 -> 12.34 */
-
-        memmove(&start[point + 1], &start[point], length - point);
-        start[point] = '.';
-
-        return length + 1;
+size_t njs_i64toa(char *buf, int64_t n)
+{
+    if (n >= 0) {
+        return njs_u64toa(buf, n);
+    } else {
+        buf[0] = '-';
+        return njs_u64toa(buf + 1, -(uint64_t)n) + 1;
     }
+}
 
-    if (-6 < point && point <= 0) {
-
-        /* 1234e-6 -> 0.001234 */
-
-        offset = 2 - point;
-        memmove(&start[offset], start, length);
-
-        start[0] = '0';
-        start[1] = '.';
-
-        if (offset - 2 > 0) {
-            njs_memset(&start[2], '0', offset - 2);
-        }
-
-        return length + offset;
+/* XXX: only tested for 1 <= n < 2^53 */
+size_t njs_u64toa_radix(char *buf, uint64_t n, unsigned int radix)
+{
+    int radix_bits, l;
+    if (likely(radix == 10))
+        return njs_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;
     }
+}
 
-    /* 1234e30 -> 1.234e33 */
-
-    if (length == 1) {
-
-        /* 1e30 */
-
-        start[1] = 'e';
-
-        size =  njs_write_exponent(point - 1, &start[2]);
-
-        return size + 2;
+size_t njs_i64toa_radix(char *buf, int64_t n, unsigned int radix)
+{
+    if (n >= 0) {
+        return njs_u64toa_radix(buf, n, radix);
+    } else {
+        buf[0] = '-';
+        return njs_u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1;
     }
-
-    memmove(&start[2], &start[1], length - 1);
-    start[1] = '.';
-    start[length + 1] = 'e';
-
-    size = njs_write_exponent(point - 1, &start[length + 2]);
-
-    return size + length + 2;
 }
-
-
-njs_inline size_t
-njs_dtoa_exp_format(char *start, int exponent, size_t prec, size_t len)
+#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)
 {
-    char  *p;
-
-    p = &start[len];
-    if (prec != 1) {
-        memmove(&start[2], &start[1], len - 1);
-        start[1] = '.';
-        p++;
+    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");
 
-    njs_memset(p, '0', prec - len);
-    p += prec - len;
-
-    *p++ = 'e';
+    /* 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");
 
-    return prec + 1 + (prec != 1) + njs_write_exponent(exponent, p);
+    /* 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
 
-
-njs_inline size_t
-njs_dtoa_prec_format(char *start, size_t prec, size_t len, int point)
+/* 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     exponent;
-    size_t  m, rest;
-
-    exponent = point - 1;
+    int n_digits, digits_per_limb, radix_bits, n, len;
 
-    if (exponent < -6 || exponent >= (int) prec) {
-        return njs_dtoa_exp_format(start, exponent, prec, len);
+    n_digits = n_digits1;
+    if ((radix & (radix - 1)) == 0) {
+        /* radix = 2^radix_bits */
+        radix_bits = 31 - clz32(radix);
+    } else {
+        radix_bits = 0;
     }
-
-    /* Fixed notation. */
-
-    if (point <= 0) {
-
-        /* 1234e-2 => 0.001234000 */
-
-        memmove(&start[2 + (-point)], start, len);
-        start[0] = '0';
-        start[1] = '.';
-
-        njs_memset(&start[2], '0', -point);
-
-        if (prec > len) {
-            njs_memset(&start[2 + (-point) + len], '0', prec - len);
+    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);
         }
-
-        return prec + 2 + (-point);
     }
 
-    if (point >= (int) len) {
-
-        /* TODO: (2**96).toPrecision(45) not enough precision, BigInt needed. */
-
-        njs_memset(&start[len], '0', point - len);
-
-        if (point < (int) prec) {
-            start[point] = '.';
+    /* 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;
+}
 
-            njs_memset(&start[point + 1], '0', prec - point);
+/* 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);
         }
-
-    } else if (point < (int) prec) {
-
-        /* 123456 -> 123.45600 */
-
-        m = njs_min((int) len, point);
-        rest = njs_min(len, prec) - m;
-        memmove(&start[m + 1], &start[m], rest);
-
-        start[m] = '.';
-
-        njs_memset(&start[m + rest + 1], '0', prec - m - rest);
     }
-
-    return prec + (point < (int) prec);
+    return e_offset;
 }
 
-
-size_t
-njs_dtoa(double value, char *start)
+/* 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     point, minus;
-    char    *p;
-    size_t  length;
-
-    /* Not handling NaN and inf. */
+    int e_offset;
 
-    minus = 0;
-    p = start;
-
-    if (value == 0) {
-        *p++ = '0';
+    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 p - start;
-    }
+/* 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 (signbit(value)) {
-        *p++ = '-';
-        value = -value;
-        minus = 1;
+    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;
+}
 
-    length = njs_grisu2(value, p, &point);
+/* 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;
 
-    return njs_dtoa_format(p, length, point) + minus;
+    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];
 
-/*
- * TODO: For prec > 16 result maybe rounded. To support prec > 16 Bignum
- * support is requred.
- */
-size_t
-njs_dtoa_precision(double value, char *start, size_t prec)
+void js_dtoa_dump_stats(void)
 {
-    int     point, minus;
-    char    *p;
-    size_t  length;
-
-    /* Not handling NaN and inf. */
+    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
 
-    p = start;
-    minus = 0;
+/* 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 njs_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 (value != 0) {
-        if (value < 0) {
-            *p++ = '-';
-            value = -value;
-            minus = 1;
+    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;
         }
-
-        length = njs_grisu2_prec(value, p, prec, &point);
-
     } else {
-        start[0] = '0';
-        length = 1;
-        point = 1;
+        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 njs_dtoa_prec_format(p, prec, length, point) + minus;
+    return max_int(n, 9); /* also include NaN and [-]Infinity */
 }
 
-
-size_t
-njs_dtoa_exponential(double value, char *start, njs_int_t frac)
+#if defined(__SANITIZE_ADDRESS__) && 0
+static void *dtoa_malloc(uint64_t **pptr, size_t size)
 {
-    int     point, minus;
-    char    *p;
-    size_t  length;
-
-    /* Not handling NaN and inf. */
+    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;
+}
 
-    p = start;
-    minus = 0;
+static void dtoa_free(void *ptr)
+{
+}
+#endif
 
-    if (value != 0) {
-        if (value < 0) {
-            *p++ = '-';
-            value = -value;
-            minus = 1;
+/* return the length */
+int njs_dtoa2(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);
+    njs_assert((size_t) (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 += njs_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;
+
+        njs_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;
+        njs_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 += njs_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:
+    dtoa_free(mant_max);
+    dtoa_free(tmp1);
+    return q - buf;
+}
 
-        if (frac == -1) {
-            length = njs_grisu2(value, p, &point);
+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 {
-            length = njs_grisu2_prec(value, p, frac + 1, &point);
+            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 njs_atod2(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);
+    njs_assert((size_t) (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;
+        if (!(flags & JS_ATOD_ACCEPT_PREFIX_AFTER_SIGN))
+            goto no_radix_prefix;
+    } else if (p[0] == '-') {
+        is_neg = 1;
+        p++;
+        p_start = p;
+        if (!(flags & JS_ATOD_ACCEPT_PREFIX_AFTER_SIGN))
+            goto no_radix_prefix;
     } else {
-        start[0] = '0';
-        length = 1;
-        point = 1;
+        p_start = p;
     }
-
-    if (frac == -1) {
-        frac = length - 1;
+    
+    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 {
+    no_radix_prefix:
+        if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p))
+            goto overflow;
+    }
+    if (radix == 0)
+        radix = 10;
+
+    cur_limb = 0;
+    expn_offset = 0;
+    digit_count = 0;
+    limb_digit_count = 0;
+    max_digits = atod_max_digits_table[radix - 2];
+    digits_per_limb = digits_per_limb_table[radix - 2];
+    radix_base = radix_base_table[radix - 2];
+    radix_shift = ctz32(radix);
+    radix1 = radix >> radix_shift;
+    if (radix1 == 1) {
+        /* radix = 2^radix_bits */
+        radix_bits = radix_shift;
+    } else {
+        radix_bits = 0;
+    }
+    tmp0->len = 1;
+    tmp0->tab[0] = 0;
+    extra_digits = 0;
+    pos = 0;
+    dot_pos = -1;
+    /* skip leading zeros */
+    for(;;) {
+        if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) &&
+            !(flags & JS_ATOD_INT_ONLY)) {
+            if (*p == sep)
+                goto fail;
+            if (dot_pos >= 0)
+                break;
+            dot_pos = pos;
+            p++;
+        }
+        if (*p == sep && p > p_start && p[1] == '0')
+            p++;
+        if (*p != '0')
+            break;
+        p++;
+        pos++;
+    }
+    
+    sig_pos = pos;
+    for(;;) {
+        limb_t c;
+        if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) &&
+            !(flags & JS_ATOD_INT_ONLY)) {
+            if (*p == sep)
+                goto fail;
+            if (dot_pos >= 0)
+                break;
+            dot_pos = pos;
+            p++;
+        }
+        if (*p == sep && p > p_start && to_digit(p[1]) < radix)
+            p++;
+        c = to_digit(*p);
+        if ((int) 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;
+        const char *p1;
+        p1 = p;
+        is_bin_exp = (*p1 == 'p' || *p1 == 'P');
+        p1++;
+        exp_is_neg = 0;
+        if (*p1 == '+') {
+            p1++;
+        } else if (*p1 == '-') {
+            exp_is_neg = 1;
+            p1++;
+        }
+        c = to_digit(*p1);
+        if (c >= 10)
+            goto skip_exp;
+        expn = c;
+        p1++;
+        for(;;) {
+            if (*p1 == sep && to_digit(p1[1]) < 10)
+                p1++;
+            c = to_digit(*p1);
+            if (c >= 10)
+                break;
+            if (!expn_overflow) {
+                if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) {
+                    expn_overflow = TRUE;
+                } else {
+                    expn = expn * 10 + c;
+                }
+            }
+            p1++;
+        }
+        p = p1;
+        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;
+        }
     }
 
-    return njs_dtoa_exp_format(p, point - 1, frac + 1, length) + minus;
+skip_exp:
+    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;
 }
index 4dcfc09830e1e424f3986d622e4fe0e440333b13..4eb6b214ec1909df0ac8d5981a3c7f6bc09a92f4 100644 (file)
-
 /*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) Nginx, Inc.
+ * 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.
  */
 
 #ifndef _NJS_DTOA_H_INCLUDED_
 #define _NJS_DTOA_H_INCLUDED_
 
-#define NJS_DTOA_MAX_LEN  njs_length("-1.7976931348623157e+308")
+#include <njs_types.h>
+#include <njs_clang.h>
+
+//#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)
+/* accept radix prefix (0x, 0o, 0b) after sign (+/-) */
+#define JS_ATOD_ACCEPT_PREFIX_AFTER_SIGN  (1 << 4)
+
+typedef struct {
+    uint64_t mem[37];
+} JSDTOATempMem;
+
+typedef struct {
+    uint64_t mem[27];
+} JSATODTempMem;
+
+/* return a maximum bound of the string length */
+int njs_dtoa_max_len(double d, int radix, int n_digits, int flags);
+/* return the string length */
+int njs_dtoa2(char *buf, double d, int radix, int n_digits, int flags,
+              JSDTOATempMem *tmp_mem);
+double njs_atod2(const char *str, const char **pnext, int radix, int flags,
+                 JSATODTempMem *tmp_mem);
+
+#ifdef JS_DTOA_DUMP_STATS
+void njs_dtoa_dump_stats(void);
+#endif
+
+/* additional exported functions */
+size_t njs_u32toa(char *buf, uint32_t n);
+size_t njs_i32toa(char *buf, int32_t n);
+size_t njs_u64toa(char *buf, uint64_t n);
+size_t njs_i64toa(char *buf, int64_t n);
+size_t njs_u64toa_radix(char *buf, uint64_t n, unsigned int radix);
+size_t njs_i64toa_radix(char *buf, int64_t n, unsigned int radix);
+
+
+njs_inline size_t
+njs_dtoa(double value, char *start)
+{
+    JSDTOATempMem  tmp_mem;
+
+    return njs_dtoa2(start, value, 10, 0, JS_DTOA_FORMAT_FREE, &tmp_mem);
+}
+
+/* Expects a null-terminated string. */
+njs_inline double
+njs_atod(const char *str, const char **pnext, int radix, int flags)
+{
+    JSATODTempMem  tmp_mem;
 
-NJS_EXPORT size_t njs_dtoa(double value, char *start);
-NJS_EXPORT size_t njs_dtoa_precision(double value, char *start, size_t prec);
-NJS_EXPORT size_t njs_dtoa_exponential(double value, char *start,
-    njs_int_t frac);
+    return njs_atod2(str, pnext, radix, flags, &tmp_mem);
+}
 
 #endif /* _NJS_DTOA_H_INCLUDED_ */
diff --git a/src/njs_dtoa_fixed.c b/src/njs_dtoa_fixed.c
deleted file mode 100644 (file)
index f97c4a7..0000000
+++ /dev/null
@@ -1,446 +0,0 @@
-
-/*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) NGINX, Inc.
- *
- * An internal fixed_dtoa() implementation based upon V8
- * src/numbers/fixed-dtoa.cc without bignum support.
- *
- * Copyright 2011 the V8 project authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file.
- */
-
-#include <njs_main.h>
-#include <njs_diyfp.h>
-
-
-typedef struct {
-    uint64_t  high;
-    uint64_t  low;
-} njs_diyu128_t;
-
-
-#define njs_diyu128(_h, _l)       (njs_diyu128_t) { .high = (_h), .low = (_l) }
-
-
-njs_inline njs_diyu128_t
-njs_diyu128_mul(njs_diyu128_t v, uint32_t multiplicand)
-{
-    uint32_t  part;
-    uint64_t  accumulator;
-
-    accumulator = (v.low & UINT32_MAX) * multiplicand;
-    part = (uint32_t) (accumulator & UINT32_MAX);
-
-    accumulator >>= 32;
-    accumulator = accumulator + (v.low >> 32) * multiplicand;
-    v.low = (accumulator << 32) + part;
-
-    accumulator >>= 32;
-    accumulator = accumulator + (v.high & UINT32_MAX) * multiplicand;
-    part = (uint32_t) (accumulator & UINT32_MAX);
-
-    accumulator >>= 32;
-    accumulator = accumulator + (v.high >> 32) * multiplicand;
-    v.high = (accumulator << 32) + part;
-
-    return v;
-}
-
-
-njs_inline njs_diyu128_t
-njs_diyu128_shift(njs_diyu128_t v, njs_int_t shift)
-{
-    /* -64 <= shift <= 64.*/
-
-    if (shift < 0) {
-        if (shift == -64) {
-            v.high = v.low;
-            v.low = 0;
-
-        } else {
-            v.high <<= -shift;
-            v.high += v.low >> (64 + shift);
-            v.low <<= -shift;
-        }
-
-        return v;
-    }
-
-    if (shift > 0) {
-        if (shift == 64) {
-            v.low = v.high;
-            v.high = 0;
-
-        } else {
-            v.low >>= shift;
-            v.low += v.high << (64 - shift);
-            v.high >>= shift;
-        }
-    }
-
-    return v;
-}
-
-
-njs_inline njs_int_t
-njs_diyu128_div_mod_pow2(njs_diyu128_t *v, njs_int_t power)
-{
-    uint64_t   part_low, part_high;
-    njs_int_t  result;
-
-    if (power >= 64) {
-        result = (int) (v->high >> (power - 64));
-        v->high -= (uint64_t) result << (power - 64);
-
-        return result;
-    }
-
-    part_low = v->low >> power;
-    part_high = v->high << (64 - power);
-
-    result = (int) (part_low + part_high);
-
-    v->low -= part_low << power;
-    v->high = 0;
-
-    return result;
-}
-
-
-njs_inline njs_bool_t
-njs_diyu128_is_zero(njs_diyu128_t v)
-{
-    if (v.low == 0 && v.high == 0) {
-        return 1;
-    }
-
-    return 0;
-}
-
-
-njs_inline njs_uint_t
-njs_diyu128_bit_at(njs_diyu128_t v, njs_uint_t pos)
-{
-    if (pos >= 64) {
-        return (njs_uint_t) (v.high >> (pos - 64)) & 1;
-    }
-
-    return (njs_uint_t) (v.low >> pos) & 1;
-}
-
-
-static size_t
-njs_fill_digits32(uint32_t number, char *start, size_t length)
-{
-    char       c;
-    size_t     i, j, n;
-    njs_int_t  digit;
-
-    n = 0;
-
-    while (number != 0) {
-        digit = number % 10;
-        number /= 10;
-        start[length + n] = '0' + digit;
-        n++;
-    }
-
-    i = length;
-    j = length + n - 1;
-
-    while (i < j) {
-        c = start[i];
-        start[i] = start[j];
-        start[j] = c;
-
-        i++;
-        j--;
-    }
-
-    return length + n;
-}
-
-
-njs_inline size_t
-njs_fill_digits32_fixed_length(uint32_t number, size_t requested_length,
-    char *start, size_t length)
-{
-    size_t  i;
-
-    i = requested_length;
-
-    while (i-- > 0) {
-        start[length + i] = '0' + number % 10;
-        number /= 10;
-    }
-
-    return length + requested_length;
-}
-
-
-njs_inline size_t
-njs_fill_digits64(uint64_t number, char *start, size_t length)
-{
-    uint32_t  part0, part1, part2, ten7;
-
-    ten7 = 10000000;
-
-    part2 = (uint32_t) (number % ten7);
-    number /= ten7;
-
-    part1 = (uint32_t) (number % ten7);
-    part0 = (uint32_t) (number / ten7);
-
-    if (part0 != 0) {
-        length = njs_fill_digits32(part0, start, length);
-        length = njs_fill_digits32_fixed_length(part1, 7, start, length);
-        return njs_fill_digits32_fixed_length(part2, 7, start, length);
-    }
-
-    if (part1 != 0) {
-        length = njs_fill_digits32(part1, start, length);
-        return njs_fill_digits32_fixed_length(part2, 7, start, length);
-    }
-
-    return njs_fill_digits32(part2, start, length);
-}
-
-
-njs_inline size_t
-njs_fill_digits64_fixed_length(uint64_t number, char *start, size_t length)
-{
-    uint32_t  part0, part1, part2, ten7;
-
-    ten7 = 10000000;
-
-    part2 = (uint32_t) (number % ten7);
-    number /= ten7;
-
-    part1 = (uint32_t) (number % ten7);
-    part0 = (uint32_t) (number / ten7);
-
-    length = njs_fill_digits32_fixed_length(part0, 3, start, length);
-    length = njs_fill_digits32_fixed_length(part1, 7, start, length);
-
-    return njs_fill_digits32_fixed_length(part2, 7, start, length);
-}
-
-
-njs_inline size_t
-njs_dtoa_round_up(char *start, size_t length, njs_int_t *point)
-{
-    size_t  i;
-
-    if (length == 0) {
-        start[0] = '1';
-        *point = 1;
-        return 1;
-    }
-
-    start[length - 1]++;
-
-    for (i = length - 1; i > 0; --i) {
-        if (start[i] != '0' + 10) {
-            return length;
-        }
-
-        start[i] = '0';
-        start[i - 1]++;
-    }
-
-    if (start[0] == '0' + 10) {
-        start[0] = '1';
-        (*point)++;
-    }
-
-    return length;
-}
-
-
-static size_t
-njs_fill_fractionals(uint64_t fractionals, int exponent, njs_uint_t frac,
-    char *start, size_t length, njs_int_t *point)
-{
-    njs_int_t      n, digit;
-    njs_uint_t     i;
-    njs_diyu128_t  fractionals128;
-
-    /*
-     * -128 <= exponent <= 0.
-     * 0 <= fractionals * 2^exponent < 1.
-     */
-
-    if (-exponent <= 64) {
-        /* fractionals <= 2^56. */
-
-        n = -exponent;
-
-        for (i = 0; i < frac && fractionals != 0; ++i) {
-            /*
-             * Multiplication by 10 is replaced with multiplication by 5 and
-             * point location adjustment. To avoid integer-overflow.
-             */
-
-            fractionals *= 5;
-            n--;
-
-            digit = (njs_int_t) (fractionals >> n);
-            fractionals -= (uint64_t) digit << n;
-
-            start[length++] = '0' + digit;
-        }
-
-        if (n > 0 && ((fractionals >> (n - 1)) & 1)) {
-            length = njs_dtoa_round_up(start, length, point);
-        }
-
-    } else {
-
-        fractionals128 = njs_diyu128(fractionals, 0);
-        fractionals128 = njs_diyu128_shift(fractionals128, -exponent - 64);
-
-        n = 128;
-
-        for (i = 0; i < frac && !njs_diyu128_is_zero(fractionals128); ++i) {
-            /*
-             * Multiplication by 10 is replaced with multiplication by 5 and
-             * point location adjustment. To avoid integer-overflow.
-             */
-
-            fractionals128 = njs_diyu128_mul(fractionals128, 5);
-            n--;
-
-            digit = njs_diyu128_div_mod_pow2(&fractionals128, n);
-
-            start[length++] = '0' + digit;
-        }
-
-        if (njs_diyu128_bit_at(fractionals128, n - 1)) {
-            length = njs_dtoa_round_up(start, length, point);
-        }
-    }
-
-    return length;
-}
-
-
-njs_inline size_t
-njs_trim_zeroes(char *start, size_t length, njs_int_t *point)
-{
-    size_t  i, n;
-
-    while (length > 0 && start[length - 1] == '0') {
-        length--;
-    }
-
-    n = 0;
-
-    while (n < length && start[n] == '0') {
-        n++;
-    }
-
-    if (n != 0) {
-        for (i = n; i < length; ++i) {
-            start[i - n] = start[i];
-        }
-
-        length -= n;
-        *point -= n;
-    }
-
-    return length;
-}
-
-
-size_t
-njs_fixed_dtoa(double value, njs_uint_t frac, char *start, njs_int_t *point)
-{
-    size_t       length;
-    uint32_t     quotient;
-    uint64_t     significand, divisor, dividend, remainder, integral, fract;
-    njs_int_t    exponent;
-    njs_diyfp_t  v;
-
-    length = 0;
-    v = njs_d2diyfp(value);
-
-    significand = v.significand;
-    exponent = v.exp;
-
-    /* exponent <= 19. */
-
-    if (exponent + NJS_SIGNIFICAND_SIZE > 64) {
-        /* exponent > 11. */
-
-        divisor = njs_uint64(0xB1, 0xA2BC2EC5); /* 5 ^ 17 */
-
-        dividend = significand;
-
-        if (exponent > 17) {
-            /* (e - 17) <= 3. */
-            dividend <<= exponent - 17;
-
-            quotient = (uint32_t) (dividend / divisor);
-            remainder = (dividend % divisor) << 17;
-
-        } else {
-            divisor <<= 17 - exponent;
-
-            quotient = (uint32_t) (dividend / divisor);
-            remainder = (dividend % divisor) << exponent;
-        }
-
-        length = njs_fill_digits32(quotient, start, length);
-        length = njs_fill_digits64_fixed_length(remainder, start, length);
-        *point = (njs_int_t) length;
-
-    } else if (exponent >= 0) {
-        /* 0 <= exponent <= 11. */
-
-        significand <<= exponent;
-        length = njs_fill_digits64(significand, start, length);
-        *point = (njs_int_t) length;
-
-    } else if (exponent > -NJS_SIGNIFICAND_SIZE) {
-        /* -53 < exponent < 0. */
-
-        integral = significand >> -exponent;
-        fract = significand - (integral << -exponent);
-
-        if (integral > UINT32_MAX) {
-            length = njs_fill_digits64(integral, start, length);
-
-        } else {
-            length = njs_fill_digits32((uint32_t) integral, start, length);
-        }
-
-        *point = (njs_int_t) length;
-        length = njs_fill_fractionals(fract, exponent, frac, start, length,
-                                      point);
-
-    } else if (exponent < -128) {
-        /* Valid for frac =< 20 only. TODO: bignum support. */
-
-        start[0] = '\0';
-
-        *point = -frac;
-
-    } else {
-        /* -128 <= exponent <= -53. */
-
-        *point = 0;
-        length = njs_fill_fractionals(significand, exponent, frac, start,
-                                      length, point);
-    }
-
-    length = njs_trim_zeroes(start, length, point);
-    start[length] = '\0';
-
-    if (length == 0) {
-        *point = -frac;
-    }
-
-    return length;
-}
diff --git a/src/njs_dtoa_fixed.h b/src/njs_dtoa_fixed.h
deleted file mode 100644 (file)
index b601182..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-
-/*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) Nginx, Inc.
- */
-
-#ifndef _NJS_DTOA_FIXED_H_INCLUDED_
-#define _NJS_DTOA_FIXED_H_INCLUDED_
-
-NJS_EXPORT size_t njs_fixed_dtoa(double value, njs_uint_t frac, char *start,
-    njs_int_t *point);
-
-#endif /* _NJS_DTOA_FIXED_H_INCLUDED_ */
index d77befdde67a755f00777a4295658f2701b27216..1e5f0c406ac1e6d0dbb4780a03c24284e7a77c35 100644 (file)
@@ -751,30 +751,17 @@ static const u_char *
 njs_json_parse_number(njs_json_parse_ctx_t *ctx, njs_value_t *value,
     const u_char *p)
 {
-    double        num;
-    njs_int_t     sign;
-    const u_char  *start;
+    double      num;
+    const char  *start, *end;
 
-    sign = 1;
+    start = (const char *) p;
+    num = njs_atod((const char *) p, &end, 10, 0);
 
-    if (*p == '-') {
-        if (p + 1 == ctx->end) {
-            goto error;
-        }
-
-        p++;
-        sign = -1;
-    }
-
-    start = p;
-    num = njs_number_dec_parse(&p, ctx->end, 0);
-    if (p != start) {
-        njs_set_number(value, sign * num);
-        return p;
+    if (end != start && !isnan(num)) {
+        njs_set_number(value, num);
+        return (const u_char *) end;
     }
 
-error:
-
     njs_json_parse_exception(ctx, "Unexpected number", p);
 
     return NULL;
index 4131301de9a6098ec1caba472e44ade7f25328b8..932f701ed3264b183be9ea27464e9c173fccda66 100644 (file)
@@ -813,80 +813,52 @@ njs_lexer_string(njs_lexer_t *lexer, njs_lexer_token_t *token, u_char quote)
 static void
 njs_lexer_number(njs_lexer_t *lexer, njs_lexer_token_t *token)
 {
-    u_char        c;
+    int           flags;
     const u_char  *p;
 
-    c = lexer->start[-1];
-    p = lexer->start;
-
     token->text.start = lexer->start - 1;
+    p = token->text.start;
 
-    if (c == '0' && p != lexer->end) {
+    flags = JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_ACCEPT_UNDERSCORES;
 
-        /* Hexadecimal literal values. */
+    if (p[0] == '0' && (p + 1) < lexer->end) {
+        /* Legacy octal literal. */
 
-        if (*p == 'x' || *p == 'X') {
-            p++;
+        if (p[1] == '_' || (p[1] >= '0' && p[1] <= '9')) {
+            for (p++; p < lexer->end; p++) {
+                if (*p == '_' || (*p >= '0' && *p <= '9')) {
+                    continue;
+                }
 
-            if (p == lexer->end || njs_char_to_hex(*p) < 0) {
-                goto illegal_token;
+                break;
             }
 
-            token->number = njs_number_hex_parse(&p, lexer->end, 1);
-
-            goto done;
+            goto illegal_token;
         }
 
-        /* Octal literal values. */
-
-        if (*p == 'o' || *p == 'O') {
-            p++;
-
-            if (p == lexer->end || (u_char)(*p - '0') > 7) {
-                goto illegal_token;
-            }
-
-            token->number = njs_number_oct_parse(&p, lexer->end, 1);
-
-            if (p < lexer->end && (*p == '8' || *p == '9')) {
-                goto illegal_trailer;
-            }
-
-            goto done;
+        if ((p[1] == 'x' || p[1] == 'X'
+                || p[1] == 'b' || p[1] == 'B'
+                || p[1] == 'o' || p[1] == 'O'))
+        {
+            flags |= JS_ATOD_INT_ONLY;
         }
+    }
 
-        /* Binary literal values. */
-
-        if (*p == 'b' || *p == 'B') {
-            p++;
-
-            if (p == lexer->end || (u_char)(*p - '0') > 1) {
-                goto illegal_token;
-            }
+    token->number = njs_atod((const char *) p, (const char **) &p, 0, flags);
 
-            token->number = njs_number_bin_parse(&p, lexer->end, 1);
+#define njs_continuation_char(c)                                              \
+     (njs_tokens[c] == NJS_TOKEN_LETTER || njs_tokens[c] == NJS_TOKEN_DIGIT)
 
-            if (p < lexer->end && (*p >= '2' && *p <= '9')) {
-                goto illegal_trailer;
+    if (isnan(token->number) || (p < lexer->end && njs_continuation_char(*p))) {
+        while (p < lexer->end) {
+            if (!njs_continuation_char(*p)) {
+                break;
             }
 
-            goto done;
-        }
-
-        /* Legacy Octal literals are deprecated. */
-
-        if ((*p >= '0' && *p <= '9') || *p == '_') {
-            goto illegal_trailer;
+            p++;
         }
-    }
 
-    p--;
-    token->number = njs_number_dec_parse(&p, lexer->end, 1);
-
-done:
-
-    if (p[-1] == '_') {
-        p--;
+        goto illegal_token;
     }
 
     lexer->start = (u_char *) p;
@@ -896,10 +868,6 @@ done:
 
     return;
 
-illegal_trailer:
-
-    p++;
-
 illegal_token:
 
     token->text.length = p - token->text.start;
index e40fe0859532e8886494a62cb518dcf56702689c..dfe169a04fddfa1637f786163dc6d37d323a89ea 100644 (file)
 #include <njs_unicode.h>
 #include <njs_utf8.h>
 #include <njs_utf16.h>
-#include <njs_diyfp.h>
 #include <njs_dtoa.h>
-#include <njs_dtoa_fixed.h>
-#include <njs_strtod.h>
 #include <njs_djb_hash.h>
 #include <njs_murmur_hash.h>
 #include <njs_trace.h>
index 15f086510f7eb606b5e4a546d337a3d4c44da40d..dc2001d21d036d5b30c1faa26ce45539b4796c1b 100644 (file)
@@ -53,84 +53,6 @@ njs_key_to_index(const njs_value_t *value)
 }
 
 
-double
-njs_number_dec_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal)
-{
-    return njs_strtod(start, end, literal);
-}
-
-
-double
-njs_number_oct_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal)
-{
-    u_char        c;
-    double        num;
-    const u_char  *p, *_;
-
-    p = *start;
-
-    num = 0;
-    _ = p - 1;
-
-    for (; p < end; p++) {
-        /* Values less than '0' become >= 208. */
-        c = *p - '0';
-
-        if (njs_slow_path(c > 7)) {
-            if (literal && *p == '_' && (p - _) > 1) {
-                _ = p;
-                continue;
-            }
-
-            break;
-        }
-
-        num = num * 8 + c;
-    }
-
-    *start = p;
-
-    return num;
-}
-
-
-double
-njs_number_bin_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal)
-{
-    u_char        c;
-    double        num;
-    const u_char  *p, *_;
-
-    p = *start;
-
-    num = 0;
-    _ = p - 1;
-
-    for (; p < end; p++) {
-        /* Values less than '0' become >= 208. */
-        c = *p - '0';
-
-        if (njs_slow_path(c > 1)) {
-            if (literal && *p == '_' && (p - _) > 1) {
-                _ = p;
-                continue;
-            }
-
-            break;
-        }
-
-        num = num * 2 + c;
-    }
-
-    *start = p;
-
-    return num;
-}
-
-
 double
 njs_number_hex_parse(const u_char **start, const u_char *end,
     njs_bool_t literal)
@@ -165,54 +87,6 @@ njs_number_hex_parse(const u_char **start, const u_char *end,
 }
 
 
-static double
-njs_number_radix_parse(const u_char **start, const u_char *end, uint8_t radix)
-{
-    uint8_t       d;
-    double        num, n;
-    const u_char  *p;
-
-    static const int8_t  digits[256]
-        njs_aligned(32) =
-    {
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
-        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
-        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
-        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
-        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-    };
-
-    num = NAN;
-    n = 0;
-
-    for (p = *start; p < end; p++) {
-        d = digits[*p];
-
-        if (njs_slow_path(d >= radix)) {
-            break;
-        }
-
-        n = (n * radix) + d;
-        num = n;
-    }
-
-    *start = p;
-
-    return num;
-}
-
-
 njs_int_t
 njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number)
@@ -553,13 +427,13 @@ static njs_int_t
 njs_number_prototype_to_fixed(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
 {
-    u_char       *p;
-    int64_t      frac;
-    double       number;
-    size_t       length, size;
-    njs_int_t    ret, point, prefix, postfix;
-    njs_value_t  *value;
-    u_char       buf[128], buf2[128];
+    double         number;
+    size_t         size;
+    int64_t        frac;
+    njs_int_t      ret;
+    njs_value_t    *value;
+    JSDTOATempMem  tmp_mem;
+    u_char         buf[128];
 
     /* 128 > 100 + 21 + njs_length(".-\0"). */
 
@@ -592,59 +466,10 @@ njs_number_prototype_to_fixed(njs_vm_t *vm, njs_value_t *args,
         return njs_number_to_string(vm, retval, value);
     }
 
-    point = 0;
-    length = njs_fixed_dtoa(number, (njs_int_t) frac, (char *) buf, &point);
-
-    prefix = 0;
-    postfix = 0;
-
-    if (point <= 0) {
-        prefix = -point + 1;
-        point = 1;
-    }
-
-    if (prefix + (njs_int_t) length < point + frac) {
-        postfix = point + frac - length - prefix;
-    }
-
-    size = prefix + length + postfix + !!(number < 0);
-
-    if (frac > 0) {
-        size += njs_length(".");
-    }
-
-    p = buf2;
-
-    while (--prefix >= 0) {
-        *p++ = '0';
-    }
-
-    if (length != 0) {
-        p = njs_cpymem(p, buf, length);
-    }
-
-    while (--postfix >= 0) {
-        *p++ = '0';
-    }
+    size = njs_dtoa2((char *) buf, number, 10, (int) frac,
+                     JS_DTOA_FORMAT_FRAC, &tmp_mem);
 
-    p = njs_string_alloc(vm, retval, size, size);
-    if (njs_slow_path(p == NULL)) {
-        return NJS_ERROR;
-    }
-
-    if (number < 0) {
-        *p++ = '-';
-    }
-
-    p = njs_cpymem(p, buf2, point);
-
-    if (frac > 0) {
-        *p++ = '.';
-
-        memcpy(p, &buf2[point], frac);
-    }
-
-    return NJS_OK;
+    return njs_string_new(vm, retval, buf, size, size);
 }
 
 
@@ -652,12 +477,13 @@ static njs_int_t
 njs_number_prototype_to_precision(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
 {
-    double       number;
-    size_t       size;
-    int64_t      precision;
-    njs_int_t    ret;
-    njs_value_t  *value;
-    u_char       buf[128];
+    double         number;
+    size_t         size;
+    int64_t        precision;
+    njs_int_t      ret;
+    njs_value_t    *value;
+    JSDTOATempMem  tmp_mem;
+    u_char         buf[128];
 
     /* 128 > 100 + 21 + njs_length(".-\0"). */
 
@@ -694,7 +520,8 @@ njs_number_prototype_to_precision(njs_vm_t *vm, njs_value_t *args,
         return NJS_ERROR;
     }
 
-    size = njs_dtoa_precision(number, (char *) buf, (size_t) precision);
+    size = njs_dtoa2((char *) buf, number, 10, (int) precision,
+                     JS_DTOA_FORMAT_FIXED, &tmp_mem);
 
     return njs_string_new(vm, retval, buf, size, size);
 }
@@ -704,12 +531,14 @@ static njs_int_t
 njs_number_prototype_to_exponential(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
 {
-    double       number;
-    size_t       size;
-    int64_t      frac;
-    njs_int_t    ret;
-    njs_value_t  *value, *value_frac;
-    u_char       buf[128];
+    int            digits, flags;
+    double         number;
+    size_t         size;
+    int64_t        frac;
+    njs_int_t      ret;
+    njs_value_t    *value, *value_frac;
+    JSDTOATempMem  tmp_mem;
+    u_char         buf[128];
 
     value = &args[0];
 
@@ -747,131 +576,51 @@ njs_number_prototype_to_exponential(njs_vm_t *vm, njs_value_t *args,
         frac = -1;
     }
 
-    size = njs_dtoa_exponential(number, (char *) buf, (njs_int_t) frac);
+    if (frac < 0) {
+        digits = 0;
+        flags = JS_DTOA_FORMAT_FREE;
+
+    } else {
+        digits = frac + 1;
+        flags = JS_DTOA_FORMAT_FIXED;
+    }
+
+    size = njs_dtoa2((char *) buf, number, 10, digits,
+                     flags | JS_DTOA_EXP_ENABLED, &tmp_mem);
 
     return njs_string_new(vm, retval, buf, size, size);
 }
 
 
 /*
- * The radix equal to 2 produces the longest  value for a number.
+ * njs_dtoa_max_len() caps radix conversions (format free, no exponent) at 1088
+ * characters plus the terminating null, so a fixed 1.1KB stack buffer is safe.
  */
-
-#define NJS_STRING_RADIX_INTERGRAL_LEN  (1 + 1024)
-#define NJS_STRING_RADIX_FRACTION_LEN   (1 + 1075)
-#define NJS_STRING_RADIX_LEN                                                  \
-    (NJS_STRING_RADIX_INTERGRAL_LEN + NJS_STRING_RADIX_FRACTION_LEN)
-
-
-njs_inline double
-njs_number_next_double(double n)
-{
-    njs_diyfp_t  v;
-
-    v = njs_d2diyfp(n);
-
-    if (signbit(n)) {
-        if (v.significand == 0) {
-            return 0.0;
-        }
-
-        v.significand--;
-
-    } else {
-        v.significand++;
-    }
-
-    return njs_diyfp2d(v);
-}
-
+#define NJS_NUMBER_RADIX_BUF_SIZE  1100
 
 static njs_int_t
 njs_number_to_string_radix(njs_vm_t *vm, njs_value_t *string,
     double number, uint32_t radix)
 {
-    int       digit;
-    char      ch;
-    double    n, remainder, integer, fraction, delta;
-    u_char    *p, *end;
-    uint32_t  size;
-    u_char    buf[NJS_STRING_RADIX_LEN];
-
-    static const char  *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
-
-    p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
-    end = p;
-
-    n = number;
-
-    if (n < 0) {
-        n = -n;
-    }
-
-    integer = floor(n);
-    fraction = n - integer;
-
-    delta = 0.5 * (njs_number_next_double(n) - n);
-    delta = njs_max(njs_number_next_double(0.0), delta);
-
-    if (fraction >= delta && delta != 0) {
-        *p++ = '.';
-
-        do {
-            fraction *= radix;
-            delta *= radix;
-
-            digit = (int) fraction;
-            *p++ = digits[digit];
-
-            fraction -= digit;
-
-            if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1)))
-                && (fraction + delta > 1))
-            {
-                while (p-- != buf) {
-                    if (p == buf + NJS_STRING_RADIX_INTERGRAL_LEN) {
-                        integer += 1;
-                        break;
-                    }
-
-                    ch = *p;
-                    digit = (ch > '9') ? ch - 'a' + 10 : ch - '0';
-
-                    if ((uint32_t) (digit + 1) < radix) {
-                        *p++ = digits[digit + 1];
-                        break;
-                    }
-                }
-
-                break;
-            }
-
-        } while (fraction >= delta);
-
-        end = p;
-    }
-
-    p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
-
-    while (njs_d2diyfp(integer / radix).exp > 0) {
-        integer /= radix;
-        *(--p) = '0';
+    int            len;
+    size_t         size;
+    u_char         buf[NJS_NUMBER_RADIX_BUF_SIZE];
+    njs_int_t      ret;
+    JSDTOATempMem  tmp_mem;
+
+    len = njs_dtoa_max_len(number, (int) radix, 0,
+                           JS_DTOA_FORMAT_FREE | JS_DTOA_EXP_DISABLED);
+    if (njs_slow_path((size_t) len + 1 > NJS_NUMBER_RADIX_BUF_SIZE)) {
+        njs_internal_error(vm, "radix buffer overflow");
+        return NJS_ERROR;
     }
 
-    do {
-        remainder = fmod(integer, radix);
-        *(--p) = digits[(int) remainder];
-        integer = (integer - remainder) / radix;
-
-    } while (integer > 0);
-
-    if (number < 0) {
-        *(--p) = '-';
-    }
+    size = njs_dtoa2((char *) buf, number, (int) radix, 0,
+                     JS_DTOA_FORMAT_FREE | JS_DTOA_EXP_DISABLED, &tmp_mem);
 
-    size = (uint32_t) (end - p);
+    ret = njs_string_new(vm, string, buf, (uint32_t) size, (uint32_t) size);
 
-    return njs_string_new(vm, string, p, size, size);
+    return ret;
 }
 
 
@@ -951,13 +700,11 @@ njs_number_parse_int(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     double             num;
     int32_t            radix;
     njs_int_t          ret;
-    njs_bool_t         minus, test_prefix;
     njs_value_t        *value, lvalue;
-    const u_char       *p, *end;
     njs_string_prop_t  string;
 
     num = NAN;
-
+    radix = 0;
     value = njs_lvalue_arg(&lvalue, args, nargs, 1);
 
     ret = njs_value_to_string(vm, value, value);
@@ -965,28 +712,6 @@ njs_number_parse_int(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         return ret;
     }
 
-    (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);
-
-    if (string.size == 0) {
-        goto done;
-    }
-
-    p = string.start;
-    end = p + string.size;
-
-    minus = 0;
-
-    if (p[0] == '-') {
-        p++;
-        minus = 1;
-
-    } else if (p[0] == '+') {
-        p++;
-    }
-
-    test_prefix = (end - p > 1);
-    radix = 0;
-
     if (nargs > 2) {
         ret = njs_value_to_int32(vm, njs_argument(args, 2), &radix);
         if (njs_slow_path(ret != NJS_OK)) {
@@ -997,25 +722,13 @@ njs_number_parse_int(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
             if (radix < 2 || radix > 36) {
                 goto done;
             }
-
-            if (radix != 16) {
-                test_prefix = 0;
-            }
         }
     }
 
-    if (radix == 0) {
-        radix = 10;
-    }
-
-    if (test_prefix && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
-        p += 2;
-        radix = 16;
-    }
-
-    num = njs_number_radix_parse(&p, end, radix);
+    (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);
 
-    num = minus ? -num : num;
+    num = njs_atod((char *) string.start, NULL, radix,
+                   JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_PREFIX_AFTER_SIGN);
 
 done:
 
@@ -1032,8 +745,6 @@ njs_number_parse_float(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     double             num;
     njs_int_t          ret;
     njs_value_t        *value, lvalue;
-    njs_bool_t         minus;
-    const u_char       *p, *start, *end;
     njs_string_prop_t  string;
 
     value = njs_lvalue_arg(&lvalue, args, nargs, 1);
@@ -1045,42 +756,9 @@ njs_number_parse_float(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);
 
-    p = string.start;
-    end = p + string.size;
-
-    minus = 0;
-
-    if (p == end) {
-        num = NAN;
-        goto done;
-    }
-
-    if (*p == '+') {
-        p++;
-
-    } else if (*p == '-') {
-        p++;
-        minus = 1;
-    }
-
-    start = p;
-    num = njs_number_dec_parse(&p, end, 0);
-
-    if (p == start) {
-        if (p + njs_length("Infinity") > end
-            || memcmp(p, "Infinity", njs_length("Infinity")) != 0)
-        {
-            num = NAN;
-            goto done;
-        }
-
-        num = INFINITY;
-        p += njs_length("Infinity");
-    }
-
-done:
+    num = njs_atod((char *) string.start, NULL, 10, 0);
 
-    njs_set_number(retval, minus ? -num : num);
+    njs_set_number(retval, num);
 
     return NJS_OK;
 }
index d4c7a28625bae311a573cf70370047b62afe6008..2659105ef2c67abd30dd16524db67dc8fd48aa60 100644 (file)
@@ -8,18 +8,28 @@
 #define _NJS_NUMBER_H_INCLUDED_
 
 
+typedef union {
+    double      d;
+    uint64_t    u64;
+} njs_diyfp_conv_t;
+
+
 #define NJS_MAX_LENGTH      (0x1fffffffffffffLL)
 #define NJS_INT64_DBL_MIN   (-9.223372036854776e+18) /* closest to INT64_MIN */
 #define NJS_INT64_DBL_MAX   (9.223372036854776e+18) /* closest to INT64_MAX */
+#define njs_uint64(h, l)            (((uint64_t) (h) << 32) + (l))
+
+#define NJS_DBL_SIGNIFICAND_SIZE    52
+#define NJS_DBL_EXPONENT_OFFSET     ((int64_t) 0x3ff)
+#define NJS_DBL_EXPONENT_BIAS       (NJS_DBL_EXPONENT_OFFSET                  \
+                                     + NJS_DBL_SIGNIFICAND_SIZE)
 
+#define NJS_DBL_SIGNIFICAND_MASK    njs_uint64(0x000FFFFF, 0xFFFFFFFF)
+#define NJS_DBL_HIDDEN_BIT          njs_uint64(0x00100000, 0x00000000)
+#define NJS_DBL_EXPONENT_MASK       njs_uint64(0x7FF00000, 0x00000000)
+#define NJS_DBL_SIGN_MASK           njs_uint64(0x80000000, 0x00000000)
 
 double njs_key_to_index(const njs_value_t *value);
-double njs_number_dec_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal);
-double njs_number_oct_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal);
-double njs_number_bin_parse(const u_char **start, const u_char *end,
-    njs_bool_t literal);
 double njs_number_hex_parse(const u_char **start, const u_char *end,
     njs_bool_t literal);
 njs_int_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
@@ -187,7 +197,7 @@ njs_uint32_to_string(njs_vm_t *vm, njs_value_t *value, uint32_t u32)
         return NJS_ERROR;
     }
 
-    size = njs_sprintf(p, p + 10, "%uD", u32) - p;
+    size = njs_u32toa((char *) p, u32);
     value->string.data->length = size;
     value->string.data->size = size;
 
index ae68088949e0db96b6e01113fd6c9e67f84f7d34..9a36d4e00ae777697f015ea7a2a6c97b236d66eb 100644 (file)
@@ -613,7 +613,8 @@ njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_string_prop_t  string;
     char               buf[512];
 
-#define NJS_SZ_LAST    64
+#define NJS_SZ_LAST       64
+#define NJS_DTOA_MAX_LEN  25 /* njs_dtoa_max_len(d, .. JS_DTOA_FORMAT_FREE) */
 
     if (njs_is_null_or_undefined(&args[0])) {
         njs_type_error(vm, "\"this\" argument is null or undefined");
@@ -3309,73 +3310,46 @@ njs_string_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args,
 double
 njs_string_to_number(njs_vm_t *vm, const njs_value_t *value)
 {
+    int                flags;
+    size_t             len, size;
     double             num;
-    njs_bool_t         minus;
-    const u_char       *p, *start, *end;
+    const char         *next;
+    const u_char       *p;
     njs_string_prop_t  string;
 
-    const size_t  infinity = njs_length("Infinity");
-
     (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);
 
-    p = string.start;
-    end = p + string.size;
-
-    if (p == end) {
+    if (string.size == 0) {
         return 0.0;
     }
 
-    minus = 0;
+    p = string.start;
+    len = string.size;
+    flags = JS_ATOD_ACCEPT_BIN_OCT;
 
-    if (p + 2 < end && p[0] == '0'
+    if (len >= 2 && p[0] == '0'
         && (p[1] == 'x' || p[1] == 'X'
             || p[1] == 'b' || p[1] == 'B'
             || p[1] == 'o' || p[1] == 'O'))
     {
-        p += 2;
-
-        if (p[-1] == 'x' || p[-1] == 'X') {
-            num = njs_number_hex_parse(&p, end, 0);
-
-        } else if (p[-1] == 'b' || p[-1] == 'B') {
-            num = njs_number_bin_parse(&p, end, 0);
-
-        } else {
-            num = njs_number_oct_parse(&p, end, 0);
-        }
-
-    } else {
-
-        if (*p == '+') {
-            p++;
-
-        } else if (*p == '-') {
-            p++;
-            minus = 1;
-        }
-
-        start = p;
-        num = njs_number_dec_parse(&p, end, 0);
+        flags |= JS_ATOD_INT_ONLY;
+    }
 
-        if (p == start) {
-            if (p + infinity > end || memcmp(p, "Infinity", infinity) != 0) {
-                return NAN;
-            }
+    num = njs_atod((char *) string.start, &next, 0, flags);
 
-            num = INFINITY;
-            p += infinity;
-        }
-    }
+    size = (u_char *) next - p;
 
-    while (p < end) {
-        if (!njs_is_whitespace(*p)) {
-            return NAN;
+    /* Validate remaining characters are whitespace. */
+    while (size < string.size) {
+        if (!njs_is_whitespace(p[size])) {
+            num = NAN;
+            break;
         }
 
-        p++;
+        size++;
     }
 
-    return minus ? -num : num;
+    return num;
 }
 
 
@@ -3384,9 +3358,8 @@ njs_string_to_index(const njs_value_t *value)
 {
     size_t        size, len;
     double        num;
-    njs_bool_t    minus;
-    const u_char  *p, *start, *end;
-    u_char        buf[128];
+    const u_char  *start;
+    char          buf[128];
 
     if (njs_slow_path(value->type == NJS_SYMBOL)) {
         return NAN;
@@ -3399,68 +3372,13 @@ njs_string_to_index(const njs_value_t *value)
     size = value->string.data->size;
     start = value->string.data->start;
 
-    p = start;
-    end = p + size;
-    minus = 0;
-
-    if (size > 1) {
-        switch (p[0]) {
-        case '0':
-            if (size != 1) {
-                return NAN;
-            }
-
-            /* Fall through. */
-
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-            break;
-
-        case '-':
-            if (size == 2 && p[1] == '0') {
-                return -0.0;
-            }
-
-            if (size == njs_length("-Infinity")
-                && memcmp(&p[1], "Infinity", njs_length("Infinity")) == 0)
-            {
-                return -INFINITY;
-            }
-
-            p++;
-            minus = 1;
-
-            break;
-
-        case 'I':
-            if (size == njs_length("Infinity")
-                && memcmp(p, "Infinity", njs_length("Infinity")) == 0)
-            {
-                return INFINITY;
-            }
-
-            /* Fall through. */
-
-        default:
-            return NAN;
-        }
-    }
-
-    num = njs_strtod(&p, end, 0);
-    if (p != end) {
-        return NAN;
+    if (size == 2 && start[0] == '-' && start[1] == '0') {
+        return -0.0;
     }
 
-    num = minus ? -num : num;
+    num = njs_atod((char *) start, NULL, 10, 0);
 
-    len = njs_dtoa(num, (char *) buf);
+    len = njs_dtoa(num, buf);
     if (size != len || memcmp(start, buf, size) != 0) {
         return NAN;
     }
diff --git a/src/njs_strtod.c b/src/njs_strtod.c
deleted file mode 100644 (file)
index d3e48c3..0000000
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * An internal strtod() implementation based upon V8 src/strtod.cc
- * without bignum support.
- *
- * Copyright 2012 the V8 project authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file.
- */
-
-#include <njs_main.h>
-#include <njs_diyfp.h>
-
-/*
- * Max double: 1.7976931348623157 x 10^308
- * Min non-zero double: 4.9406564584124654 x 10^-324
- * Any x >= 10^309 is interpreted as +infinity.
- * Any x <= 10^-324 is interpreted as 0.
- * Note that 2.5e-324 (despite being smaller than the min double)
- * will be read as non-zero (equal to the min non-zero double).
- */
-
-#define NJS_DECIMAL_POWER_MAX           309
-#define NJS_DECIMAL_POWER_MIN           (-324)
-
-#define NJS_UINT64_MAX                  njs_uint64(0xFFFFFFFF, 0xFFFFFFFF)
-#define NJS_UINT64_DECIMAL_DIGITS_MAX   19
-
-
-/*
- * Reads digits from the buffer and converts them to a uint64.
- * Reads in as many digits as fit into a uint64.
- * When the string starts with "1844674407370955161" no further digit is read.
- * Since 2^64 = 18446744073709551616 it would still be possible read another
- * digit if it was less or equal than 6, but this would complicate the code.
- */
-njs_inline uint64_t
-njs_read_uint64(const u_char *start, size_t length, size_t *ndigits)
-{
-    u_char        d;
-    uint64_t      value;
-    const u_char  *p, *e;
-
-    value = 0;
-
-    p = start;
-    e = p + length;
-
-    while (p < e && value <= (NJS_UINT64_MAX / 10 - 1)) {
-        d = *p++ - '0';
-        value = 10 * value + d;
-    }
-
-    *ndigits = p - start;
-
-    return value;
-}
-
-
-/*
- * Reads a njs_diyfp_t from the buffer.
- * The returned njs_diyfp_t is not necessarily normalized.
- * If remaining is zero then the returned njs_diyfp_t is accurate.
- * Otherwise it has been rounded and has error of at most 1/2 ulp.
- */
-static njs_diyfp_t
-njs_diyfp_read(const u_char *start, size_t length, int *remaining)
-{
-    size_t    read;
-    uint64_t  significand;
-
-    significand = njs_read_uint64(start, length, &read);
-
-    /* Round the significand. */
-
-    if (length != read) {
-        if (start[read] >= '5') {
-            significand++;
-        }
-    }
-
-    *remaining = length - read;
-
-    return njs_diyfp(significand, 0);
-}
-
-
-/*
- * Returns 10^exp as an exact njs_diyfp_t.
- * The given exp must be in the range [1; NJS_DECIMAL_EXPONENT_DIST[.
- */
-njs_inline njs_diyfp_t
-njs_adjust_pow10(int exp)
-{
-    switch (exp) {
-    case 1:
-        return njs_diyfp(njs_uint64(0xa0000000, 00000000), -60);
-    case 2:
-        return njs_diyfp(njs_uint64(0xc8000000, 00000000), -57);
-    case 3:
-        return njs_diyfp(njs_uint64(0xfa000000, 00000000), -54);
-    case 4:
-        return njs_diyfp(njs_uint64(0x9c400000, 00000000), -50);
-    case 5:
-        return njs_diyfp(njs_uint64(0xc3500000, 00000000), -47);
-    case 6:
-        return njs_diyfp(njs_uint64(0xf4240000, 00000000), -44);
-    case 7:
-        return njs_diyfp(njs_uint64(0x98968000, 00000000), -40);
-    default:
-        njs_unreachable();
-        return njs_diyfp(0, 0);
-    }
-}
-
-
-/*
- * Returns the significand size for a given order of magnitude.
- * If v = f*2^e with 2^p-1 <= f <= 2^p then p+e is v's order of magnitude.
- * This function returns the number of significant binary digits v will have
- * once its encoded into a double. In almost all cases this is equal to
- * NJS_SIGNIFICAND_SIZE. The only exception are denormals. They start with
- * leading zeroes and their effective significand-size is hence smaller.
- */
-njs_inline int
-njs_diyfp_sgnd_size(int order)
-{
-    if (order >= (NJS_DBL_EXPONENT_DENORMAL + NJS_SIGNIFICAND_SIZE)) {
-        return NJS_SIGNIFICAND_SIZE;
-    }
-
-    if (order <= NJS_DBL_EXPONENT_DENORMAL) {
-        return 0;
-    }
-
-    return order - NJS_DBL_EXPONENT_DENORMAL;
-}
-
-
-#define NJS_DENOM_LOG   3
-#define NJS_DENOM       (1 << NJS_DENOM_LOG)
-
-/*
- * Returns either the correct double or the double that is just below
- * the correct double.
- */
-static double
-njs_diyfp_strtod(const u_char *start, size_t length, int exp)
-{
-    int          magnitude, prec_digits;
-    int          remaining, dec_exp, adj_exp, orig_e, shift;
-    int64_t      error;
-    uint64_t     prec_bits, half_way;
-    njs_diyfp_t  value, pow, adj_pow, rounded;
-
-    value = njs_diyfp_read(start, length, &remaining);
-
-    exp += remaining;
-
-    /*
-     * Since some digits may have been dropped the value is not accurate.
-     * If remaining is different than 0 than the error is at most .5 ulp
-     * (unit in the last place).
-     * Using a common denominator to avoid dealing with fractions.
-     */
-
-    error = (remaining == 0 ? 0 : NJS_DENOM / 2);
-
-    orig_e = value.exp;
-    value = njs_diyfp_normalize(value);
-    error <<= orig_e - value.exp;
-
-    if (exp < NJS_DECIMAL_EXPONENT_MIN) {
-        return 0.0;
-    }
-
-    pow = njs_cached_power_dec(exp, &dec_exp);
-
-    if (dec_exp != exp) {
-
-        adj_exp = exp - dec_exp;
-        adj_pow = njs_adjust_pow10(exp - dec_exp);
-        value = njs_diyfp_mul(value, adj_pow);
-
-        if (NJS_UINT64_DECIMAL_DIGITS_MAX - (int) length < adj_exp) {
-            /*
-             * The adjustment power is exact. There is hence only
-             * an error of 0.5.
-             */
-            error += NJS_DENOM / 2;
-        }
-    }
-
-    value = njs_diyfp_mul(value, pow);
-
-    /*
-     * The error introduced by a multiplication of a * b equals
-     *  error_a + error_b + error_a * error_b / 2^64 + 0.5
-     *  Substituting a with 'value' and b with 'pow':
-     *  error_b = 0.5  (all cached powers have an error of less than 0.5 ulp),
-     *  error_ab = 0 or 1 / NJS_DENOM > error_a * error_b / 2^64.
-     */
-
-    error += NJS_DENOM + (error != 0 ? 1 : 0);
-
-    orig_e = value.exp;
-    value = njs_diyfp_normalize(value);
-    error <<= orig_e - value.exp;
-
-    /*
-     * Check whether the double's significand changes when the error is added
-     * or substracted.
-     */
-
-    magnitude = NJS_DIYFP_SIGNIFICAND_SIZE + value.exp;
-    prec_digits = NJS_DIYFP_SIGNIFICAND_SIZE - njs_diyfp_sgnd_size(magnitude);
-
-    if (prec_digits + NJS_DENOM_LOG >= NJS_DIYFP_SIGNIFICAND_SIZE) {
-        /*
-         * This can only happen for very small denormals. In this case the
-         * half-way multiplied by the denominator exceeds the range of uint64.
-         * Simply shift everything to the right.
-         */
-        shift = prec_digits + NJS_DENOM_LOG - NJS_DIYFP_SIGNIFICAND_SIZE + 1;
-
-        value = njs_diyfp_shift_right(value, shift);
-
-        /*
-         * Add 1 for the lost precision of error, and NJS_DENOM
-         * for the lost precision of value.significand.
-         */
-        error = (error >> shift) + 1 + NJS_DENOM;
-        prec_digits -= shift;
-    }
-
-    prec_bits = value.significand & (((uint64_t) 1 << prec_digits) - 1);
-    prec_bits *= NJS_DENOM;
-
-    half_way = (uint64_t) 1 << (prec_digits - 1);
-    half_way *= NJS_DENOM;
-
-    rounded = njs_diyfp_shift_right(value, prec_digits);
-
-    if (prec_bits >= half_way + error) {
-        rounded.significand++;
-    }
-
-    return njs_diyfp2d(rounded);
-}
-
-
-static double
-njs_strtod_internal(const u_char *start, size_t length, int exp)
-{
-    int           shift;
-    size_t        left, right;
-    const u_char  *p, *e, *b;
-
-    /* Trim leading zeroes. */
-
-    p = start;
-    e = p + length;
-
-    while (p < e) {
-        if (*p != '0') {
-            start = p;
-            break;
-        }
-
-        p++;
-    }
-
-    left = e - p;
-
-    /* Trim trailing zeroes. */
-
-    b = start;
-    p = b + left - 1;
-
-    while (p > b) {
-        if (*p != '0') {
-            break;
-        }
-
-        p--;
-    }
-
-    right = p - b + 1;
-
-    length = right;
-
-    if (length == 0) {
-        return 0.0;
-    }
-
-    shift = (int) (left - right);
-
-    if (exp >= NJS_DECIMAL_POWER_MAX - shift - (int) length + 1) {
-        return INFINITY;
-    }
-
-    if (exp <= NJS_DECIMAL_POWER_MIN - shift - (int) length) {
-        return 0.0;
-    }
-
-    return njs_diyfp_strtod(start, length, exp + shift);
-}
-
-
-double
-njs_strtod(const u_char **start, const u_char *end, njs_bool_t literal)
-{
-    int           exponent, exp, insignf;
-    u_char        c, *pos;
-    njs_bool_t    minus;
-    const u_char  *e, *p, *last, *_;
-    u_char        data[128];
-
-    exponent = 0;
-    insignf = 0;
-
-    pos = data;
-    last = data + sizeof(data);
-
-    p = *start;
-    _ = p - 2;
-
-    for (; p < end; p++) {
-        /* Values less than '0' become >= 208. */
-        c = *p - '0';
-
-        if (njs_slow_path(c > 9)) {
-            if (literal) {
-                if ((p - _) == 1) {
-                    goto done;
-                }
-
-                if (*p == '_') {
-                    _ = p;
-                    continue;
-                }
-            }
-
-            break;
-        }
-
-        if (pos < last) {
-            *pos++ = *p;
-
-        } else {
-            insignf++;
-        }
-    }
-
-    /* Do not emit a '.', but adjust the exponent instead. */
-    if (p < end && *p == '.') {
-        _ = p;
-
-        for (p++; p < end; p++) {
-            /* Values less than '0' become >= 208. */
-            c = *p - '0';
-
-            if (njs_slow_path(c > 9)) {
-                if (literal && *p == '_' && (p - _) > 1) {
-                    _ = p;
-                    continue;
-                }
-
-                break;
-            }
-
-            if (pos < last) {
-                *pos++ = *p;
-                exponent--;
-
-            } else {
-                /* Ignore insignificant digits in the fractional part. */
-            }
-        }
-    }
-
-    if (pos == data) {
-        return NAN;
-    }
-
-    e = p + 1;
-
-    if (e < end && (*p == 'e' || *p == 'E')) {
-        minus = 0;
-
-        if (e + 1 < end) {
-            if (*e == '-') {
-                e++;
-                minus = 1;
-
-            } else if (*e == '+') {
-                e++;
-            }
-        }
-
-        /* Values less than '0' become >= 208. */
-        c = *e - '0';
-
-        if (njs_fast_path(c <= 9)) {
-            exp = c;
-
-            for (p = e + 1; p < end; p++) {
-                /* Values less than '0' become >= 208. */
-                c = *p - '0';
-
-                if (njs_slow_path(c > 9)) {
-                    if (literal && *p == '_' && (p - _) > 1) {
-                        _ = p;
-                        continue;
-                    }
-
-                    break;
-                }
-
-                if (exp < (INT_MAX - 9) / 10) {
-                    exp = exp * 10 + c;
-                }
-            }
-
-            exponent += minus ? -exp : exp;
-
-        } else if (literal && *e == '_') {
-            p = e;
-        }
-    }
-
-done:
-
-    *start = p;
-
-    exponent += insignf;
-
-    return njs_strtod_internal(data, pos - data, exponent);
-}
diff --git a/src/njs_strtod.h b/src/njs_strtod.h
deleted file mode 100644 (file)
index 581fdbb..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-
-/*
- * Copyright (C) Dmitry Volyntsev
- * Copyright (C) Nginx, Inc.
- */
-
-#ifndef _NJS_STRTOD_H_INCLUDED_
-#define _NJS_STRTOD_H_INCLUDED_
-
-NJS_EXPORT double njs_strtod(const u_char **start, const u_char *end,
-    njs_bool_t literal);
-
-#endif /* _NJS_STRTOD_H_INCLUDED_ */
index c38cb9dcf291250a7e91c7e9531902f1cb7f7a6f..09df4b60cf23879bfbb4190144a0802370baf653 100644 (file)
@@ -57,11 +57,6 @@ typedef uintptr_t       njs_uint_t;
 #endif
 
 
-#if (NJS_HAVE_UNSIGNED_INT128)
-typedef unsigned __int128 njs_uint128_t;
-#endif
-
-
 #if (NJS_INT_T_SIZE == 8)
 #define NJS_INT_T_LEN        NJS_INT64_T_LEN
 #define NJS_INT_T_HEXLEN     NJS_INT64_T_HEXLEN
index 5b6d556b4d59beccdd7bf1b141d10ba54cd5f428..cf266dfc9a84c570a11b2f39e1b5ffa729ea73c8 100644 (file)
@@ -287,40 +287,40 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \".\" in 1") },
 
     { njs_str("0_1"),
-      njs_str("SyntaxError: Unexpected token \"0_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0_1\" in 1") },
 
     { njs_str("1_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1_\" in 1") },
 
     { njs_str("1__0"),
-      njs_str("SyntaxError: Unexpected token \"__0\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1__0\" in 1") },
 
     { njs_str("._1"),
       njs_str("SyntaxError: Unexpected token \".\" in 1") },
 
     { njs_str(".1_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \".1_\" in 1") },
 
     { njs_str("1_.1"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1_\" in 1") },
 
     { njs_str(".0__1"),
-      njs_str("SyntaxError: Unexpected token \"__1\" in 1") },
+      njs_str("SyntaxError: Unexpected token \".0__1\" in 1") },
 
     { njs_str("1e_1"),
-      njs_str("SyntaxError: Unexpected token \"_1\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1e_1\" in 1") },
 
     { njs_str("1e-_1"),
-      njs_str("SyntaxError: Unexpected token \"_1\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1e\" in 1") },
 
     { njs_str("1E1__0"),
-      njs_str("SyntaxError: Unexpected token \"__0\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1E1__0\" in 1") },
 
     { njs_str("1_e1"),
-      njs_str("SyntaxError: Unexpected token \"_e1\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1_e1\" in 1") },
 
     { njs_str("1e1_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1e1_\" in 1") },
 
     { njs_str("-_1"),
       njs_str("ReferenceError: \"_1\" is not defined") },
@@ -352,16 +352,19 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \"0O778\" in 1") },
 
     { njs_str("0O_7"),
-      njs_str("SyntaxError: Unexpected token \"0O\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0O_7\" in 1") },
 
     { njs_str("0O + 1"),
       njs_str("SyntaxError: Unexpected token \"0O\" in 1") },
 
     { njs_str("0o7_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0o7_\" in 1") },
 
     { njs_str("0o7__7"),
-      njs_str("SyntaxError: Unexpected token \"__7\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0o7__7\" in 1") },
+
+    { njs_str("0o7.1"),
+      njs_str("SyntaxError: Unexpected token \".1\" in 1") },
 
     /* Legacy Octal Numbers are deprecated. */
 
@@ -375,16 +378,16 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \"09\" in 1") },
 
     { njs_str("0011"),
-      njs_str("SyntaxError: Unexpected token \"00\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0011\" in 1") },
 
     { njs_str("0_"),
       njs_str("SyntaxError: Unexpected token \"0_\" in 1") },
 
     { njs_str("0_1"),
-      njs_str("SyntaxError: Unexpected token \"0_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0_1\" in 1") },
 
     { njs_str("00_1"),
-      njs_str("SyntaxError: Unexpected token \"00\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"00_1\" in 1") },
 
     /* Binary Numbers. */
 
@@ -410,16 +413,19 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \"0B12\" in 1") },
 
     { njs_str("0b_11"),
-      njs_str("SyntaxError: Unexpected token \"0b\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0b_11\" in 1") },
 
     { njs_str("0b + 1"),
       njs_str("SyntaxError: Unexpected token \"0b\" in 1") },
 
     { njs_str("0B1__1"),
-      njs_str("SyntaxError: Unexpected token \"__1\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0B1__1\" in 1") },
 
     { njs_str("0b11_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0b11_\" in 1") },
+
+    { njs_str("0b1.1"),
+      njs_str("SyntaxError: Unexpected token \".1\" in 1") },
 
     /* Hex Numbers. */
 
@@ -448,19 +454,19 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected end of input in 1") },
 
     { njs_str("0x12g"),
-      njs_str("SyntaxError: Unexpected token \"g\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0x12g\" in 1") },
 
     { njs_str("0X_ff"),
-      njs_str("SyntaxError: Unexpected token \"0X\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0X_ff\" in 1") },
 
     { njs_str("0X + 1"),
       njs_str("SyntaxError: Unexpected token \"0X\" in 1") },
 
     { njs_str("0xff_"),
-      njs_str("SyntaxError: Unexpected token \"_\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0xff_\" in 1") },
 
     { njs_str("0Xf__f"),
-      njs_str("SyntaxError: Unexpected token \"__f\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"0Xf__f\" in 1") },
 
     { njs_str(""),
       njs_str("undefined") },
@@ -531,19 +537,19 @@ static njs_unit_test_t  njs_test[] =
       njs_str("-Infinity") },
 
     { njs_str("1e"),
-      njs_str("SyntaxError: Unexpected token \"e\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1e\" in 1") },
 
     { njs_str("1.e"),
-      njs_str("SyntaxError: Unexpected token \"e\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1.e\" in 1") },
 
     { njs_str("1e+"),
-      njs_str("SyntaxError: Unexpected token \"e\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1e\" in 1") },
 
     { njs_str("1.e-"),
-      njs_str("SyntaxError: Unexpected token \"e\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1.e\" in 1") },
 
     { njs_str("1eZ"),
-      njs_str("SyntaxError: Unexpected token \"eZ\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"1eZ\" in 1") },
 
     { njs_str(".e1"),
       njs_str("SyntaxError: Unexpected token \".\" in 1") },
@@ -664,9 +670,9 @@ static njs_unit_test_t  njs_test[] =
 
 #ifndef NJS_SUNC
     { njs_str("1e20.toString(14)"),
-      njs_str("33cb3bb449c2a92000") },
+      njs_str("33cb3bb449c2a90000") },
 
-    { njs_str("1.7976931348623157E+308.toString(36) == ('1a1e4vngaiqo' + '0'.repeat(187))"),
+    { njs_str("1.7976931348623157E+308.toString(36) == ('1a1e4vngail' + '0'.repeat(188))"),
       njs_str("true") },
 
     /* Largest positive double (prev_double(INFINITY)). */
@@ -677,7 +683,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("1.2312313132,1.25a850416057383,1.293699002749414,1.3010274cab0288,1.3346da6d5d455c") },
 
     { njs_str("Array(5).fill().map((n, i) => 36 - i).map((v)=>(1e23).toString(v))"),
-      njs_str("ga894a06abs0000,o5hlsorok4y0000,128fpsprqld20000,1m1s0ajv6cmo0000,2kmg5hv19br00000") },
+      njs_str("ga894a06ac00000,o5hlsorok5a0000,128fpsprqldf0000,1m1s0ajv6cn00000,2kmg5hv19br00000") },
 #endif
 
     /* Number.prototype.toFixed(frac) method. */
@@ -736,10 +742,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("NaN.toFixed(1)"),
       njs_str("NaN") },
 
-#if 0  /* FIXME: bignum support is requred to support frac >= 20 */
     { njs_str("(2 ** -100).toFixed(100)"),
       njs_str("0.0000000000000000000000000000007888609052210118054117285652827862296732064351090230047702789306640625") },
-#endif
 
     /* Number.prototype.toPrecision(prec) method. */
 
@@ -771,7 +775,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("0.125000,0.250000,0.500000,1.00000,2.00000,4.00000,8.00000,16.0000,32.0000,64.0000,128.000,256.000") },
 
     { njs_str("Array(5).fill().map((n, i) => i+16).map((v)=>(4.1).toPrecision(v))"),
-      njs_str("4.100000000000000,4.0999999999999996,4.09999999999999964,4.099999999999999644,4.0999999999999996447") },
+      njs_str("4.100000000000000,4.0999999999999996,4.09999999999999964,4.099999999999999645,4.0999999999999996447") },
 
     { njs_str("Array(3).fill().map((n, i) => i + 19).map((v)=>(2**(-v)).toPrecision(20))"),
       njs_str("0.0000019073486328125000000,9.5367431640625000000e-7,4.7683715820312500000e-7") },
@@ -779,13 +783,11 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Array(3).fill().map((n, i) => i + 32).map((v)=>(2**(v)+0.1).toPrecision(10))"),
       njs_str("4294967296,8589934592,1.717986918e+10") },
 
-#if 0  /* FIXME: bignum support is requred to support prec >= 20 */
     { njs_str("(1/7).toPrecision(100)"),
       njs_str("0.1428571428571428492126926812488818541169166564941406250000000000000000000000000000000000000000000000") },
 
     { njs_str("(2**128).toPrecision(40)"),
       njs_str("340282366920938463463374607431768211456.0") },
-#endif
 
     { njs_str("(2**128).toPrecision(1)"),
       njs_str("3e+38") },
@@ -794,7 +796,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("3.4e+38") },
 
     { njs_str("(2**128).toPrecision(40)"),
-      njs_str("340282366920938463490000000000000000000.0") },
+      njs_str("340282366920938463463374607431768211456.0") },
 
     { njs_str("(123).toPrecision(0)"),
       njs_str("RangeError: precision argument must be between 1 and 100") },
@@ -857,15 +859,13 @@ static njs_unit_test_t  njs_test[] =
       njs_str("-6.66667e-1,1.33333e+0,-2.00000e+0,2.66667e+0,-3.33333e+0,4.00000e+0") },
 
     { njs_str("Array(5).fill().map((n, i) => i + 1).map((v)=>((Math.pow(-1,v))*(2*v)/3).toExponential())"),
-      njs_str("-6.666666666666666e-1,1.3333333333333333e+0,-2e+0,2.6666666666666667e+0,-3.3333333333333337e+0") },
+      njs_str("-6.666666666666666e-1,1.3333333333333333e+0,-2e+0,2.6666666666666665e+0,-3.3333333333333335e+0") },
 
     { njs_str("1.7976931348623157e+308.toExponential()"),
       njs_str("1.7976931348623157e+308") },
 
-#if 0  /* FIXME: bignum support is requred to support prec >= 20 */
     { njs_str("(1/7).toExponential(100)"),
       njs_str("1.4285714285714284921269268124888185411691665649414062500000000000000000000000000000000000000000000000e-1") },
-#endif
 
     { njs_str("var v = 1.7976931348623157e+308; Number(v.toExponential()) == v"),
       njs_str("true") },
@@ -1319,10 +1319,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("9007199254740993 | 0"),
       njs_str("0") },
 
-#if 0
     { njs_str("9223372036854775808 | 0"),
       njs_str("0") },
-#endif
 
     { njs_str("9223372036854777856 | 0"),
       njs_str("2048") },
@@ -1366,10 +1364,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("-2147483648 << 0"),
       njs_str("-2147483648") },
 
-#if 0
     { njs_str("9223372036854775808 << 0"),
       njs_str("0") },
-#endif
 
     { njs_str("9223372036854777856 << 0"),
       njs_str("2048") },
@@ -1401,10 +1397,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("-2147483648 >> -1"),
       njs_str("-1") },
 
-#if 0
     { njs_str("9223372036854775808 >> 0"),
       njs_str("0") },
-#endif
 
     { njs_str("9223372036854777856 >> 0"),
       njs_str("2048") },
@@ -1421,10 +1415,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("NaN >>> 1"),
       njs_str("0") },
 
-#if 0
     { njs_str("9223372036854775808 >>> 1"),
       njs_str("0") },
-#endif
 
     { njs_str("-1 >>> 0"),
       njs_str("4294967295") },
@@ -1438,10 +1430,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("-2147483648 >>> -1"),
       njs_str("1") },
 
-#if 0
     { njs_str("9223372036854775808 >>> 0"),
       njs_str("0") },
-#endif
 
     { njs_str("9223372036854777856 >>> 0"),
       njs_str("2048") },
@@ -1525,10 +1515,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("-2147483648 & 65536"),
       njs_str("0") },
 
-#if 0
     { njs_str("9223372036854775808 & 65536"),
       njs_str("0") },
-#endif
 
     { njs_str("NaN & 65536"),
       njs_str("0") },
@@ -1542,10 +1530,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("-2147483648 ^ 65536"),
       njs_str("-2147418112") },
 
-#if 0
     { njs_str("9223372036854775808 ^ 65536"),
       njs_str("65536") },
-#endif
 
     { njs_str("NaN ^ 65536"),
       njs_str("65536") },
@@ -2966,7 +2952,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \")\" in 1") },
 
     { njs_str("for(9A=>>"),
-      njs_str("SyntaxError: Unexpected token \"A\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"9A\" in 1") },
 
     { njs_str("for(A?{,"),
       njs_str("SyntaxError: Unexpected token \",\" in 1") },
@@ -6872,7 +6858,7 @@ static njs_unit_test_t  njs_test[] =
               "2172814084,75727489,2189624325,84181890,"
               "-2122153212,75727489,-2105342971,84181890,"
               "-4.794245620412925e-38,3.091780090135418e-36,-1.9251027092506622e-37,6.230764342760857e-36,"
-              "-2.159546358334202e-301,5.447603729090798e-270,-1.4538065947240604e-296,3.72581468952343e-265") },
+              "-2.159546358334202e-301,5.447603729090798e-270,-1.4538065947240603e-296,3.72581468952343e-265") },
 
     { njs_str("var u8 = new Uint8Array(10);"
               "var dv = new DataView(u8.buffer, 1);"
@@ -10344,7 +10330,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("1") },
 
     { njs_str("(function f() { return 2.toString(); })()"),
-      njs_str("SyntaxError: Unexpected token \"toString\" in 1") },
+      njs_str("SyntaxError: Unexpected token \"2.toString\" in 1") },
 
     { njs_str("(function f() { return 2..toString(); })()"),
       njs_str("2") },
@@ -13487,12 +13473,24 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Number('0B111')"),
       njs_str("7") },
 
+    { njs_str("Number('0B111 ')"),
+      njs_str("7") },
+
+    { njs_str("Number('0B111  1')"),
+      njs_str("NaN") },
+
     { njs_str("Number('0b1_11')"),
       njs_str("NaN") },
 
     { njs_str("Number('-0b111')"),
       njs_str("NaN") },
 
+    { njs_str("Number('0b111.')"),
+      njs_str("NaN") },
+
+    { njs_str("Number('0b111.1')"),
+      njs_str("NaN") },
+
     { njs_str("Number(123)"),
       njs_str("123") },
 
@@ -13505,24 +13503,42 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Number('0O123')"),
       njs_str("83") },
 
+    { njs_str("Number('0O123   ')"),
+      njs_str("83") },
+
     { njs_str("Number('0o1_23')"),
       njs_str("NaN") },
 
     { njs_str("Number('-0o123')"),
       njs_str("NaN") },
 
+    { njs_str("Number('0o123.')"),
+      njs_str("NaN") },
+
+    { njs_str("Number('0o123.4')"),
+      njs_str("NaN") },
+
     { njs_str("Number('0x123')"),
       njs_str("291") },
 
     { njs_str("Number('0X123')"),
       njs_str("291") },
 
+    { njs_str("Number('0X123   ')"),
+      njs_str("291") },
+
     { njs_str("Number('0x1_23')"),
       njs_str("NaN") },
 
     { njs_str("Number('-0x123')"),
       njs_str("NaN") },
 
+    { njs_str("Number('0x123.')"),
+      njs_str("NaN") },
+
+    { njs_str("Number('0x123.4')"),
+      njs_str("NaN") },
+
     { njs_str("['1', ' 1 ', '1\\t', '1\\n', '1\\r\\n'].reduce((a, x) => a + Number(x), 0)"),
       njs_str("5") },
 
@@ -13530,7 +13546,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("0.1111111111111111") },
 
     { njs_str("Number('1'.repeat(128))"),
-      njs_str("1.1111111111111113e+127") },
+      njs_str("1.1111111111111112e+127") },
 
     { njs_str("Number('1'.repeat(129))"),
       njs_str("1.1111111111111112e+128") },
@@ -18267,6 +18283,12 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("parseFloat('-5.7e+abc')"),
       njs_str("-5.7") },
 
+    { njs_str("parseFloat('5' + '0'.repeat(200))"),
+      njs_str("5e+200") },
+
+    { njs_str("parseFloat('500_000_000')"),
+      njs_str("500") },
+
     /* debugger. */
 
     { njs_str("debugger"),
@@ -18589,7 +18611,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected number at position 0") },
 
     { njs_str("JSON.parse('--')"),
-      njs_str("SyntaxError: Unexpected number at position 1") },
+      njs_str("SyntaxError: Unexpected number at position 0") },
 
     { njs_str("JSON.parse('1-')"),
       njs_str("SyntaxError: Unexpected token at position 1") },
@@ -20207,6 +20229,9 @@ static njs_unit_test_t  njs_denormals_test[] =
     { njs_str("2.2250738585072014E-308.toString(2) == ('0.' + '0'.repeat(1021) + '1')"),
       njs_str("true") },
 
+    { njs_str("Number.MAX_VALUE.toString(2) === ('1'.repeat(53) + '0'.repeat(971))"),
+      njs_str("true") },
+
     { njs_str("Number('2.2250738585072014E-323')"),
       njs_str("2.5e-323") },
 
@@ -20225,7 +20250,7 @@ static njs_unit_test_t  njs_denormals_test[] =
     { njs_str("var zeros = count => '0'.repeat(count);"
               "["
               "  [1.8858070859709815e-308, `0.${zeros(1022)}1101100011110111011100000100011001111101110001010111`],"
-              // FIXME: "  [Number.MIN_VALUE, `0.${zeros(1073)}1`]"
+              "  [Number.MIN_VALUE, `0.${zeros(1021)}1`],"
               "  [-5.06631661953108e-309, `-0.${zeros(1024)}11101001001010000001101111010101011111111011010111`],"
               "  [6.22574126804e-313, `0.${zeros(1037)}11101010101101100111000110100111001`],"
               "  [-4e-323, `-0.${zeros(1070)}1`],"
@@ -20242,40 +20267,6 @@ static njs_unit_test_t  njs_denormals_test[] =
 };
 
 
-static njs_unit_test_t  njs_disabled_denormals_test[] =
-{
-    { njs_str("Number('2.2250738585072014E-323')"),
-      njs_str("0") },
-
-    { njs_str("Number('2.2250738585072014E-323') + 0"),
-      njs_str("0") },
-
-    /* Smallest positive double (next_double(0)). */
-    { njs_str("5E-324.toString(36)"),
-      njs_str("0") },
-
-    { njs_str("2.2250738585072014E-323.toString(2)"),
-      njs_str("0") },
-
-    /* Smallest normal double. */
-
-    { njs_str("2.2250738585072014e-308"),
-      njs_str("2.2250738585072014e-308") },
-
-    { njs_str("2.2250738585072014e-308/2"),
-      njs_str("0") },
-
-    /* Denormals. */
-    { njs_str("["
-              "1.8858070859709815e-308,"
-              "-5.06631661953108e-309,"
-              "6.22574126804e-313,"
-              "-4e-323,"
-              "].map(v=>v.toString(2))"),
-      njs_str("0,0,0,0") },
-};
-
-
 static njs_unit_test_t  njs_fs_module_test[] =
 {
     { njs_str("var fs = require('fs'); typeof fs"),
@@ -23313,22 +23304,6 @@ typedef struct {
 } njs_test_suite_t;
 
 
-static njs_int_t
-njs_disabled_denormals_tests(njs_unit_test_t tests[], size_t num,
-    njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat)
-{
-    njs_int_t  ret;
-
-    njs_mm_denormals(0);
-
-    ret = njs_unit_test(tests, num, name, opts, stat);
-
-    njs_mm_denormals(1);
-
-    return ret;
-}
-
-
 static njs_test_suite_t  njs_suites[] =
 {
     { njs_str("script"),
@@ -23349,17 +23324,6 @@ static njs_test_suite_t  njs_suites[] =
       njs_nitems(njs_denormals_test),
       njs_unit_test },
 
-    {
-#if (NJS_HAVE_DENORMALS_CONTROL)
-        njs_str("disabled denormals"),
-#else
-        njs_str(""),
-#endif
-      { .repeat = 1, .unsafe = 1 },
-      njs_disabled_denormals_test,
-      njs_nitems(njs_disabled_denormals_test),
-      njs_disabled_denormals_tests },
-
     { njs_str("module"),
       { .repeat = 1, .module = 1, .unsafe = 1 },
       njs_module_test,
@@ -23462,8 +23426,6 @@ main(int argc, char **argv)
 
     tzset();
 
-    njs_mm_denormals(1);
-
     njs_memzero(&stat, sizeof(njs_stat_t));
 
     for (i = 0; i < njs_nitems(njs_suites); i++) {