From e981736c13157d1ff16c65ad8f4499fcb8a6bce5 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 14 Nov 2025 13:07:20 -0800 Subject: [PATCH 1/3] Fixed --debug=YES build after d4cedc0. --- src/njs_date.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/njs_date.c b/src/njs_date.c index 4577d9bdd..57d92ee34 100644 --- a/src/njs_date.c +++ b/src/njs_date.c @@ -1407,9 +1407,11 @@ njs_date_prototype_to_primitive(njs_vm_t *vm, njs_value_t *args, } } - ret = njs_atom_atomize_key(vm, &args[1]); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + if (args[1].atom_id == NJS_ATOM_STRING_unknown) { + ret = njs_atom_atomize_key(vm, &args[1]); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } switch (args[1].atom_id) { From 26c9363c727e646b701413988c7b7ab9db838919 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 14 Nov 2025 18:22:56 -0800 Subject: [PATCH 2/3] Ensuring string values are zero-terminated. This helps when some code requires a string value to be zero-terminated to avoid copying and adding \0 byte. It adds small overhead (less than 3% in worst case of an empty string value) to existing sizeof(njs_value_t) + sizeof(njs_string_t) = 32. --- src/njs_scope.c | 4 +++- src/njs_string.c | 5 ++++- src/njs_value.h | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/njs_scope.c b/src/njs_scope.c index cd1176a29..9d7b5776d 100644 --- a/src/njs_scope.c +++ b/src/njs_scope.c @@ -199,7 +199,7 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, + njs_string_map_size(length); } - value_size += sizeof(njs_string_t) + size; + value_size += sizeof(njs_string_t) + size + 1; } value_size += sizeof(njs_index_t); @@ -221,6 +221,8 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, string->length = src->string.data->length; string->size = src->string.data->size; + string->start[size] = '\0'; + memcpy(string->start, start, size); } diff --git a/src/njs_string.c b/src/njs_string.c index 1a2b23338..89e97cf6e 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -147,6 +147,7 @@ njs_string_new(njs_vm_t *vm, njs_value_t *value, const u_char *start, } +/* Underlying string data is zero-terminated. */ u_char * njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size, uint64_t length) @@ -172,7 +173,7 @@ njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size, total = size; } - string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t) + total); + string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t) + total + 1); if (njs_fast_path(string != NULL)) { value->string.data = string; @@ -181,6 +182,8 @@ njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size, string->size = size; string->length = length; + string->start[size] = '\0'; + if (map_offset != 0) { map = (uint32_t *) (string->start + map_offset); map[0] = 0; diff --git a/src/njs_value.h b/src/njs_value.h index 61a46c0a8..1422f02cc 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -517,6 +517,7 @@ typedef struct { njs_assert((value)->string.data != NULL); \ (str)->length = (value)->string.data->size; \ (str)->start = (u_char *) (value)->string.data->start; \ + njs_assert((str)->start[(str)->length] == '\0'); \ } while (0) From ea2565291f8a771686c0f12292a56b2215ec58f9 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Thu, 23 Oct 2025 20:06:42 -0700 Subject: [PATCH 3/3] Using printing and parsing library from QuickJS. - 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. --- auto/clang | 25 - auto/sources | 3 - auto/types | 54 -- external/njs_shell.c | 8 - src/njs.h | 2 + src/njs_clang.h | 18 - src/njs_cutils.h | 149 +++ src/njs_diyfp.c | 149 --- src/njs_diyfp.h | 214 ----- src/njs_dtoa.c | 1971 ++++++++++++++++++++++++++++---------- src/njs_dtoa.h | 110 ++- src/njs_dtoa_fixed.c | 446 --------- src/njs_dtoa_fixed.h | 13 - src/njs_json.c | 27 +- src/njs_lexer.c | 86 +- src/njs_main.h | 3 - src/njs_number.c | 446 ++------- src/njs_number.h | 24 +- src/njs_string.c | 138 +-- src/njs_strtod.c | 438 --------- src/njs_strtod.h | 13 - src/njs_types.h | 5 - src/test/njs_unit_test.c | 210 ++-- 23 files changed, 1947 insertions(+), 2605 deletions(-) create mode 100644 src/njs_cutils.h delete mode 100644 src/njs_diyfp.c delete mode 100644 src/njs_diyfp.h delete mode 100644 src/njs_dtoa_fixed.c delete mode 100644 src/njs_dtoa_fixed.h delete mode 100644 src/njs_strtod.c delete mode 100644 src/njs_strtod.h diff --git a/auto/clang b/auto/clang index 27fe03b5a..965fe7fa3 100644 --- a/auto/clang +++ b/auto/clang @@ -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 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 - int main(void) { - _mm_setcsr(_mm_getcsr()); - return 0; - }" -. auto/feature diff --git a/auto/sources b/auto/sources index 1a89edf57..24fd7696c 100644 --- a/auto/sources +++ b/auto/sources @@ -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 \ diff --git a/auto/types b/auto/types index 6e1c2c61a..7bb9c16ad 100644 --- a/auto/types +++ b/auto/types @@ -118,57 +118,3 @@ njs_feature_test="#include 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 diff --git a/external/njs_shell.c b/external/njs_shell.c index e459f5a65..d4132d28f 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -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; diff --git a/src/njs.h b/src/njs.h index b4fb2617e..a2781a446 100644 --- 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); diff --git a/src/njs_clang.h b/src/njs_clang.h index af9f3350b..3eadd875d 100644 --- a/src/njs_clang.h +++ b/src/njs_clang.h @@ -209,24 +209,6 @@ njs_unsafe_cast_double_to_int64(double num) } -#if (NJS_HAVE_DENORMALS_CONTROL) -#include - -/* - * 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 index 000000000..4f14052a3 --- /dev/null +++ b/src/njs_cutils.h @@ -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 +#include + +#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 index 37ed6906a..000000000 --- a/src/njs_diyfp.c +++ /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 -#include - - -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 index cfc3423b5..000000000 --- a/src/njs_diyfp.h +++ /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_ */ diff --git a/src/njs_dtoa.c b/src/njs_dtoa.c index 7d8e9e444..7f0244000 100644 --- a/src/njs_dtoa.c +++ b/src/njs_dtoa.c @@ -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 @@ -26,632 +15,1606 @@ * * 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 -#include -#include +#include +/* + 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> 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; } diff --git a/src/njs_dtoa.h b/src/njs_dtoa.h index 4dcfc0983..4eb6b214e 100644 --- a/src/njs_dtoa.h +++ b/src/njs_dtoa.h @@ -1,17 +1,111 @@ - /* - * 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 +#include + +//#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 index f97c4a724..000000000 --- a/src/njs_dtoa_fixed.c +++ /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 -#include - - -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 index b60118246..000000000 --- a/src/njs_dtoa_fixed.h +++ /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_ */ diff --git a/src/njs_json.c b/src/njs_json.c index d77befdde..1e5f0c406 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -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; diff --git a/src/njs_lexer.c b/src/njs_lexer.c index 4131301de..932f701ed 100644 --- a/src/njs_lexer.c +++ b/src/njs_lexer.c @@ -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 @@ njs_lexer_number(njs_lexer_t *lexer, njs_lexer_token_t *token) return; -illegal_trailer: - - p++; - illegal_token: token->text.length = p - token->text.start; diff --git a/src/njs_main.h b/src/njs_main.h index e40fe0859..dfe169a04 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -17,10 +17,7 @@ #include #include #include -#include #include -#include -#include #include #include #include diff --git a/src/njs_number.c b/src/njs_number.c index 15f086510..dc2001d21 100644 --- a/src/njs_number.c +++ b/src/njs_number.c @@ -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; } diff --git a/src/njs_number.h b/src/njs_number.h index d4c7a2862..2659105ef 100644 --- a/src/njs_number.h +++ b/src/njs_number.h @@ -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; diff --git a/src/njs_string.c b/src/njs_string.c index 89e97cf6e..c5ce872f9 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -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 index d3e48c385..000000000 --- a/src/njs_strtod.c +++ /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 -#include - -/* - * 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 index 581fdbb0d..000000000 --- a/src/njs_strtod.h +++ /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_ */ diff --git a/src/njs_types.h b/src/njs_types.h index c38cb9dcf..09df4b60c 100644 --- a/src/njs_types.h +++ b/src/njs_types.h @@ -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 diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 0c5e55cc0..94deaf70e 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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);" @@ -10341,7 +10327,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") }, @@ -13484,12 +13470,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") }, @@ -13502,24 +13500,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") }, @@ -13527,7 +13543,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") }, @@ -18264,6 +18280,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"), @@ -18586,7 +18608,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") }, @@ -20204,6 +20226,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") }, @@ -20222,7 +20247,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`]," @@ -20239,40 +20264,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"), @@ -23310,22 +23301,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"), @@ -23346,17 +23321,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, @@ -23459,8 +23423,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++) {