From 7e4611f6dc8705032f2bbdc7eebe86059af99d88 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Thu, 19 Jul 2018 18:11:52 +0300 Subject: [PATCH] Fixed Number.toString(). Adding correct dtoa() and strtod() realization. This fixes #28 and #30 issues on GitHub. --- Makefile | 6 + njs/njs_core.h | 2 + njs/njs_json.c | 2 +- njs/njs_number.c | 116 +---------- njs/njs_number.h | 1 - njs/test/njs_unit_test.c | 74 +++++-- nxt/Makefile | 36 ++++ nxt/auto/clang | 26 +++ nxt/nxt_clang.h | 32 ++++ nxt/nxt_diyfp.c | 150 +++++++++++++++ nxt/nxt_diyfp.h | 212 ++++++++++++++++++++ nxt/nxt_dtoa.c | 363 +++++++++++++++++++++++++++++++++++ nxt/nxt_dtoa.h | 12 ++ nxt/nxt_strtod.c | 403 +++++++++++++++++++++++++++++++++++++++ nxt/nxt_strtod.h | 12 ++ nxt/nxt_types.h | 5 + 16 files changed, 1322 insertions(+), 130 deletions(-) create mode 100644 nxt/nxt_diyfp.c create mode 100644 nxt/nxt_diyfp.h create mode 100644 nxt/nxt_dtoa.c create mode 100644 nxt/nxt_dtoa.h create mode 100644 nxt/nxt_strtod.c create mode 100644 nxt/nxt_strtod.h diff --git a/Makefile b/Makefile index b1cc0531..476fbcd7 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,9 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_parser_expression.o \ $(NXT_BUILDDIR)/njs_generator.o \ $(NXT_BUILDDIR)/njs_disassembler.o \ + $(NXT_BUILDDIR)/nxt_diyfp.o \ + $(NXT_BUILDDIR)/nxt_dtoa.o \ + $(NXT_BUILDDIR)/nxt_strtod.o \ $(NXT_BUILDDIR)/nxt_djb_hash.o \ $(NXT_BUILDDIR)/nxt_utf8.o \ $(NXT_BUILDDIR)/nxt_array.o \ @@ -76,6 +79,9 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_parser_expression.o \ $(NXT_BUILDDIR)/njs_generator.o \ $(NXT_BUILDDIR)/njs_disassembler.o \ + $(NXT_BUILDDIR)/nxt_diyfp.o \ + $(NXT_BUILDDIR)/nxt_dtoa.o \ + $(NXT_BUILDDIR)/nxt_strtod.o \ $(NXT_BUILDDIR)/nxt_djb_hash.o \ $(NXT_BUILDDIR)/nxt_utf8.o \ $(NXT_BUILDDIR)/nxt_array.o \ diff --git a/njs/njs_core.h b/njs/njs_core.h index e288ef00..11c9cc9d 100644 --- a/njs/njs_core.h +++ b/njs/njs_core.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/njs/njs_json.c b/njs/njs_json.c index 3d14948a..a45741fe 100644 --- a/njs/njs_json.c +++ b/njs/njs_json.c @@ -1844,7 +1844,7 @@ njs_json_append_number(njs_json_stringify_t *stringify, njs_value_t *value) return NXT_ERROR; } - size = njs_num_to_buf(num, p, 64); + size = nxt_dtoa(num, (char *) p); njs_json_buf_written(stringify, size); } diff --git a/njs/njs_number.c b/njs/njs_number.c index c821c718..d70d4fc8 100644 --- a/njs/njs_number.c +++ b/njs/njs_number.c @@ -66,91 +66,7 @@ njs_value_to_index(const njs_value_t *value) double njs_number_dec_parse(const u_char **start, const u_char *end) { - u_char c; - double num, frac, scale, exponent; - nxt_bool_t minus; - const u_char *e, *p; - - p = *start; - - num = 0; - - while (p < end) { - /* Values less than '0' become >= 208. */ - c = *p - '0'; - - if (nxt_slow_path(c > 9)) { - break; - } - - num = num * 10 + c; - p++; - } - - if (p < end && *p == '.') { - - frac = 0; - scale = 1; - - for (p++; p < end; p++) { - /* Values less than '0' become >= 208. */ - c = *p - '0'; - - if (nxt_slow_path(c > 9)) { - break; - } - - frac = frac * 10 + c; - scale *= 10; - } - - num += frac / scale; - } - - 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 (nxt_fast_path(c <= 9)) { - exponent = c; - p = e + 1; - - while (p < end) { - /* Values less than '0' become >= 208. */ - c = *p - '0'; - - if (nxt_slow_path(c > 9)) { - break; - } - - exponent = exponent * 10 + c; - p++; - } - - if (num != 0) { - exponent = minus ? -exponent : exponent; - num = num * pow(10.0, exponent); - } - } - } - - *start = p; - - return num; + return nxt_strtod(start, end); } @@ -312,7 +228,7 @@ njs_number_to_string(njs_vm_t *vm, njs_value_t *string, } } else { - size = njs_num_to_buf(num, buf, sizeof(buf)); + size = nxt_dtoa(num, (char *) buf); return njs_string_new(vm, string, buf, size, size); } @@ -323,34 +239,6 @@ njs_number_to_string(njs_vm_t *vm, njs_value_t *string, } -size_t -njs_num_to_buf(double num, u_char *buf, size_t size) -{ - double n; - const char *fmt; - - n = fabs(num); - - if (n == 0) { - fmt = "%g"; - - } else if (n < 1) { - fmt = "%f"; - - } else if (n < 1000000) { - fmt = "%g"; - - } else if (n < 1e20) { - fmt = "%1.f"; - - } else { - fmt = "%1.e"; - } - - return snprintf((char *) buf, size, fmt, num); -} - - njs_ret_t njs_number_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) diff --git a/njs/njs_number.h b/njs/njs_number.h index a50b9719..33cef22b 100644 --- a/njs/njs_number.h +++ b/njs/njs_number.h @@ -20,7 +20,6 @@ int64_t njs_number_radix_parse(const u_char **start, const u_char *end, uint8_t radix); njs_ret_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string, const njs_value_t *number); -size_t njs_num_to_buf(double num, u_char *buf, size_t size); njs_ret_t njs_number_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); njs_ret_t njs_number_global_is_nan(njs_vm_t *vm, njs_value_t *args, diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 16da060a..5143b8e2 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -93,19 +93,44 @@ static njs_unit_test_t njs_test[] = /* Numbers. */ + { nxt_string("0"), + nxt_string("0") }, + + { nxt_string("-0"), + nxt_string("-0") }, + + { nxt_string("0.1"), + nxt_string("0.1") }, + + { nxt_string("0.000001"), + nxt_string("0.000001") }, + + { nxt_string("0.00000123456"), + nxt_string("0.00000123456") }, + + { nxt_string("0.0000001"), + nxt_string("1e-7") }, + + { nxt_string("1.1000000"), + nxt_string("1.1") }, + + { nxt_string("99999999999999999999"), + nxt_string("100000000000000000000") }, + + { nxt_string("99999999999999999999.111"), + nxt_string("100000000000000000000") }, + { nxt_string("999999999999999999999"), nxt_string("1e+21") }, -#if 0 { nxt_string("9223372036854775808"), - nxt_string("9223372036854775808") }, + nxt_string("9223372036854776000") }, { nxt_string("18446744073709551616"), nxt_string("18446744073709552000") }, { nxt_string("1.7976931348623157E+308"), nxt_string("1.7976931348623157e+308") }, -#endif { nxt_string("+1"), nxt_string("1") }, @@ -223,16 +248,16 @@ static njs_unit_test_t njs_test[] = nxt_string("57") }, { nxt_string("5.7e-1"), - nxt_string("0.570000") }, + nxt_string("0.57") }, { nxt_string("-5.7e-1"), - nxt_string("-0.570000") }, + nxt_string("-0.57") }, { nxt_string("1.1e-01"), - nxt_string("0.110000") }, + nxt_string("0.11") }, { nxt_string("5.7e-2"), - nxt_string("0.057000") }, + nxt_string("0.057") }, { nxt_string("1.1e+01"), nxt_string("11") }, @@ -629,7 +654,7 @@ static njs_unit_test_t njs_test[] = nxt_string("NaN") }, { nxt_string("var a = 0.1; a **= -2"), - nxt_string("100") }, + nxt_string("99.99999999999999") }, { nxt_string("var a = 1; a **= NaN"), nxt_string("NaN") }, @@ -6210,6 +6235,24 @@ static njs_unit_test_t njs_test[] = { nxt_string("Number('123')"), nxt_string("123") }, + { nxt_string("Number('0.'+'1'.repeat(128))"), + nxt_string("0.1111111111111111") }, + + { nxt_string("Number('1'.repeat(128))"), + nxt_string("1.1111111111111113e+127") }, + + { nxt_string("Number('1'.repeat(129))"), + nxt_string("1.1111111111111112e+128") }, + + { nxt_string("Number('1'.repeat(129))"), + nxt_string("1.1111111111111112e+128") }, + + { nxt_string("Number('1'.repeat(129)+'e-100')"), + nxt_string("1.1111111111111112e+28") }, + + { nxt_string("Number('1'.repeat(310))"), + nxt_string("Infinity") }, + { nxt_string("var o = { valueOf: function() { return 123 } };" "Number(o)"), nxt_string("123") }, @@ -7530,7 +7573,7 @@ static njs_unit_test_t njs_test[] = /* Math. */ { nxt_string("Math.PI"), - nxt_string("3.14159") }, + nxt_string("3.141592653589793") }, { nxt_string("Math.abs()"), nxt_string("NaN") }, @@ -7788,8 +7831,8 @@ static njs_unit_test_t njs_test[] = { nxt_string("Math.cbrt(-Infinity)"), nxt_string("-Infinity") }, - { nxt_string("Math.cbrt('27')"), - nxt_string("3") }, + { nxt_string("(Math.cbrt('27') - 3) < 1e-15"), + nxt_string("true") }, { nxt_string("Math.cbrt(-1)"), nxt_string("-1") }, @@ -8570,10 +8613,10 @@ static njs_unit_test_t njs_test[] = nxt_string("57") }, { nxt_string("parseFloat('-5.7e-1')"), - nxt_string("-0.570000") }, + nxt_string("-0.57") }, { nxt_string("parseFloat('-5.e-1')"), - nxt_string("-0.500000") }, + nxt_string("-0.5") }, { nxt_string("parseFloat('5.7e+01')"), nxt_string("57") }, @@ -8582,7 +8625,7 @@ static njs_unit_test_t njs_test[] = nxt_string("57") }, { nxt_string("parseFloat('-5.7e-1abc')"), - nxt_string("-0.570000") }, + nxt_string("-0.57") }, { nxt_string("parseFloat('-5.7e')"), nxt_string("-5.7") }, @@ -8914,6 +8957,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("JSON.stringify(123)"), nxt_string("123") }, + { nxt_string("JSON.stringify(0.00000123)"), + nxt_string("0.00000123") }, + { nxt_string("JSON.stringify(new Number(123))"), nxt_string("123") }, diff --git a/nxt/Makefile b/nxt/Makefile index 37b7e02c..00dc9053 100644 --- a/nxt/Makefile +++ b/nxt/Makefile @@ -4,6 +4,9 @@ NXT_LIB = nxt $(NXT_BUILDDIR)/libnxt.a: \ $(NXT_LIB)/nxt_auto_config.h \ + $(NXT_BUILDDIR)/nxt_diyfp.o \ + $(NXT_BUILDDIR)/nxt_dtoa.o \ + $(NXT_BUILDDIR)/nxt_strtod.o \ $(NXT_BUILDDIR)/nxt_djb_hash.o \ $(NXT_BUILDDIR)/nxt_utf8.o \ $(NXT_BUILDDIR)/nxt_array.o \ @@ -20,6 +23,9 @@ $(NXT_BUILDDIR)/libnxt.a: \ $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \ ar -r -c $(NXT_BUILDDIR)/libnxt.a \ + $(NXT_BUILDDIR)/nxt_diyfp.o \ + $(NXT_BUILDDIR)/nxt_dtoa.o \ + $(NXT_BUILDDIR)/nxt_strtod.o \ $(NXT_BUILDDIR)/nxt_djb_hash.o \ $(NXT_BUILDDIR)/nxt_utf8.o \ $(NXT_BUILDDIR)/nxt_array.o \ @@ -34,6 +40,36 @@ $(NXT_BUILDDIR)/libnxt.a: \ $(NXT_BUILDDIR)/nxt_trace.o \ $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \ +$(NXT_BUILDDIR)/nxt_diyfp.o: \ + $(NXT_LIB)/nxt_types.h \ + $(NXT_LIB)/nxt_clang.h \ + $(NXT_LIB)/nxt_diyfp.h \ + $(NXT_LIB)/nxt_diyfp.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_diyfp.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) \ + $(NXT_LIB)/nxt_diyfp.c + +$(NXT_BUILDDIR)/nxt_dtoa.o: \ + $(NXT_LIB)/nxt_types.h \ + $(NXT_LIB)/nxt_clang.h \ + $(NXT_LIB)/nxt_dtoa.h \ + $(NXT_LIB)/nxt_dtoa.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_dtoa.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) \ + $(NXT_LIB)/nxt_dtoa.c + +$(NXT_BUILDDIR)/nxt_strtod.o: \ + $(NXT_LIB)/nxt_types.h \ + $(NXT_LIB)/nxt_clang.h \ + $(NXT_LIB)/nxt_strtod.h \ + $(NXT_LIB)/nxt_strtod.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_strtod.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) \ + $(NXT_LIB)/nxt_strtod.c + $(NXT_BUILDDIR)/nxt_murmur_hash.o: \ $(NXT_LIB)/nxt_types.h \ $(NXT_LIB)/nxt_clang.h \ diff --git a/nxt/auto/clang b/nxt/auto/clang index a2fe7040..45dd8c49 100644 --- a/nxt/auto/clang +++ b/nxt/auto/clang @@ -166,6 +166,18 @@ END # C language features. +nxt_feature="GCC unsigned __int128" +nxt_feature_name=NXT_HAVE_UNSIGNED_INT128 +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="int main(void) { + unsigned __int128 p = 0; + return (int) p; + }" +. ${NXT_AUTO}feature + + nxt_feature="GCC __builtin_expect()" nxt_feature_name=NXT_HAVE_BUILTIN_EXPECT nxt_feature_run=no @@ -217,6 +229,20 @@ nxt_feature_test="int main(void) { . ${NXT_AUTO}feature +nxt_feature="GCC __builtin_clzll()" +nxt_feature_name=NXT_HAVE_BUILTIN_CLZLL +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="int main(void) { + if (__builtin_clzll(1ULL) != 63) { + return 1; + } + return 0; + }" +. ${NXT_AUTO}feature + + nxt_feature="GCC __attribute__ visibility" nxt_feature_name=NXT_HAVE_GCC_ATTRIBUTE_VISIBILITY nxt_feature_run=no diff --git a/nxt/nxt_clang.h b/nxt/nxt_clang.h index 32737201..f79108ff 100644 --- a/nxt/nxt_clang.h +++ b/nxt/nxt_clang.h @@ -86,6 +86,38 @@ nxt_leading_zeros(uint32_t x) #endif +#if (NXT_HAVE_BUILTIN_CLZLL) +#define nxt_leading_zeros64(x) (((x) == 0) ? 64 : __builtin_clzll(x)) + +#else + +nxt_inline uint64_t +nxt_leading_zeros64(uint64_t x) +{ + uint64_t n; + + /* + * There is no sense to optimize this function, since almost + * all platforms nowadays support the built-in instruction. + */ + + if (x == 0) { + return 64; + } + + n = 0; + + while ((x & 0x8000000000000000) == 0) { + n++; + x <<= 1; + } + + return n; +} + +#endif + + #if (NXT_HAVE_GCC_ATTRIBUTE_VISIBILITY) #define NXT_EXPORT __attribute__((visibility("default"))) diff --git a/nxt/nxt_diyfp.c b/nxt/nxt_diyfp.c new file mode 100644 index 00000000..a6e4f1be --- /dev/null +++ b/nxt/nxt_diyfp.c @@ -0,0 +1,150 @@ + +/* + * 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 +#include +#include + + +typedef struct nxt_cpe_s { + uint64_t significand; + int16_t bin_exp; + int16_t dec_exp; +} nxt_cpe_t; + + +static const nxt_cpe_t nxt_cached_powers[] = { + { nxt_uint64(0xfa8fd5a0, 0x081c0288), -1220, -348 }, + { nxt_uint64(0xbaaee17f, 0xa23ebf76), -1193, -340 }, + { nxt_uint64(0x8b16fb20, 0x3055ac76), -1166, -332 }, + { nxt_uint64(0xcf42894a, 0x5dce35ea), -1140, -324 }, + { nxt_uint64(0x9a6bb0aa, 0x55653b2d), -1113, -316 }, + { nxt_uint64(0xe61acf03, 0x3d1a45df), -1087, -308 }, + { nxt_uint64(0xab70fe17, 0xc79ac6ca), -1060, -300 }, + { nxt_uint64(0xff77b1fc, 0xbebcdc4f), -1034, -292 }, + { nxt_uint64(0xbe5691ef, 0x416bd60c), -1007, -284 }, + { nxt_uint64(0x8dd01fad, 0x907ffc3c), -980, -276 }, + { nxt_uint64(0xd3515c28, 0x31559a83), -954, -268 }, + { nxt_uint64(0x9d71ac8f, 0xada6c9b5), -927, -260 }, + { nxt_uint64(0xea9c2277, 0x23ee8bcb), -901, -252 }, + { nxt_uint64(0xaecc4991, 0x4078536d), -874, -244 }, + { nxt_uint64(0x823c1279, 0x5db6ce57), -847, -236 }, + { nxt_uint64(0xc2109436, 0x4dfb5637), -821, -228 }, + { nxt_uint64(0x9096ea6f, 0x3848984f), -794, -220 }, + { nxt_uint64(0xd77485cb, 0x25823ac7), -768, -212 }, + { nxt_uint64(0xa086cfcd, 0x97bf97f4), -741, -204 }, + { nxt_uint64(0xef340a98, 0x172aace5), -715, -196 }, + { nxt_uint64(0xb23867fb, 0x2a35b28e), -688, -188 }, + { nxt_uint64(0x84c8d4df, 0xd2c63f3b), -661, -180 }, + { nxt_uint64(0xc5dd4427, 0x1ad3cdba), -635, -172 }, + { nxt_uint64(0x936b9fce, 0xbb25c996), -608, -164 }, + { nxt_uint64(0xdbac6c24, 0x7d62a584), -582, -156 }, + { nxt_uint64(0xa3ab6658, 0x0d5fdaf6), -555, -148 }, + { nxt_uint64(0xf3e2f893, 0xdec3f126), -529, -140 }, + { nxt_uint64(0xb5b5ada8, 0xaaff80b8), -502, -132 }, + { nxt_uint64(0x87625f05, 0x6c7c4a8b), -475, -124 }, + { nxt_uint64(0xc9bcff60, 0x34c13053), -449, -116 }, + { nxt_uint64(0x964e858c, 0x91ba2655), -422, -108 }, + { nxt_uint64(0xdff97724, 0x70297ebd), -396, -100 }, + { nxt_uint64(0xa6dfbd9f, 0xb8e5b88f), -369, -92 }, + { nxt_uint64(0xf8a95fcf, 0x88747d94), -343, -84 }, + { nxt_uint64(0xb9447093, 0x8fa89bcf), -316, -76 }, + { nxt_uint64(0x8a08f0f8, 0xbf0f156b), -289, -68 }, + { nxt_uint64(0xcdb02555, 0x653131b6), -263, -60 }, + { nxt_uint64(0x993fe2c6, 0xd07b7fac), -236, -52 }, + { nxt_uint64(0xe45c10c4, 0x2a2b3b06), -210, -44 }, + { nxt_uint64(0xaa242499, 0x697392d3), -183, -36 }, + { nxt_uint64(0xfd87b5f2, 0x8300ca0e), -157, -28 }, + { nxt_uint64(0xbce50864, 0x92111aeb), -130, -20 }, + { nxt_uint64(0x8cbccc09, 0x6f5088cc), -103, -12 }, + { nxt_uint64(0xd1b71758, 0xe219652c), -77, -4 }, + { nxt_uint64(0x9c400000, 0x00000000), -50, 4 }, + { nxt_uint64(0xe8d4a510, 0x00000000), -24, 12 }, + { nxt_uint64(0xad78ebc5, 0xac620000), 3, 20 }, + { nxt_uint64(0x813f3978, 0xf8940984), 30, 28 }, + { nxt_uint64(0xc097ce7b, 0xc90715b3), 56, 36 }, + { nxt_uint64(0x8f7e32ce, 0x7bea5c70), 83, 44 }, + { nxt_uint64(0xd5d238a4, 0xabe98068), 109, 52 }, + { nxt_uint64(0x9f4f2726, 0x179a2245), 136, 60 }, + { nxt_uint64(0xed63a231, 0xd4c4fb27), 162, 68 }, + { nxt_uint64(0xb0de6538, 0x8cc8ada8), 189, 76 }, + { nxt_uint64(0x83c7088e, 0x1aab65db), 216, 84 }, + { nxt_uint64(0xc45d1df9, 0x42711d9a), 242, 92 }, + { nxt_uint64(0x924d692c, 0xa61be758), 269, 100 }, + { nxt_uint64(0xda01ee64, 0x1a708dea), 295, 108 }, + { nxt_uint64(0xa26da399, 0x9aef774a), 322, 116 }, + { nxt_uint64(0xf209787b, 0xb47d6b85), 348, 124 }, + { nxt_uint64(0xb454e4a1, 0x79dd1877), 375, 132 }, + { nxt_uint64(0x865b8692, 0x5b9bc5c2), 402, 140 }, + { nxt_uint64(0xc83553c5, 0xc8965d3d), 428, 148 }, + { nxt_uint64(0x952ab45c, 0xfa97a0b3), 455, 156 }, + { nxt_uint64(0xde469fbd, 0x99a05fe3), 481, 164 }, + { nxt_uint64(0xa59bc234, 0xdb398c25), 508, 172 }, + { nxt_uint64(0xf6c69a72, 0xa3989f5c), 534, 180 }, + { nxt_uint64(0xb7dcbf53, 0x54e9bece), 561, 188 }, + { nxt_uint64(0x88fcf317, 0xf22241e2), 588, 196 }, + { nxt_uint64(0xcc20ce9b, 0xd35c78a5), 614, 204 }, + { nxt_uint64(0x98165af3, 0x7b2153df), 641, 212 }, + { nxt_uint64(0xe2a0b5dc, 0x971f303a), 667, 220 }, + { nxt_uint64(0xa8d9d153, 0x5ce3b396), 694, 228 }, + { nxt_uint64(0xfb9b7cd9, 0xa4a7443c), 720, 236 }, + { nxt_uint64(0xbb764c4c, 0xa7a44410), 747, 244 }, + { nxt_uint64(0x8bab8eef, 0xb6409c1a), 774, 252 }, + { nxt_uint64(0xd01fef10, 0xa657842c), 800, 260 }, + { nxt_uint64(0x9b10a4e5, 0xe9913129), 827, 268 }, + { nxt_uint64(0xe7109bfb, 0xa19c0c9d), 853, 276 }, + { nxt_uint64(0xac2820d9, 0x623bf429), 880, 284 }, + { nxt_uint64(0x80444b5e, 0x7aa7cf85), 907, 292 }, + { nxt_uint64(0xbf21e440, 0x03acdd2d), 933, 300 }, + { nxt_uint64(0x8e679c2f, 0x5e44ff8f), 960, 308 }, + { nxt_uint64(0xd433179d, 0x9c8cb841), 986, 316 }, + { nxt_uint64(0x9e19db92, 0xb4e31ba9), 1013, 324 }, + { nxt_uint64(0xeb96bf6e, 0xbadf77d9), 1039, 332 }, + { nxt_uint64(0xaf87023b, 0x9bf0ee6b), 1066, 340 }, +}; + + +#define NXT_D_1_LOG2_10 0.30102999566398114 /* 1 / log2(10). */ + + +nxt_diyfp_t +nxt_cached_power_dec(int exp, int *dec_exp) +{ + u_int index; + const nxt_cpe_t *cp; + + index = (exp + NXT_DECIMAL_EXPONENT_OFF) / NXT_DECIMAL_EXPONENT_DIST; + cp = &nxt_cached_powers[index]; + + *dec_exp = cp->dec_exp; + + return nxt_diyfp(cp->significand, cp->bin_exp); +} + + +nxt_diyfp_t +nxt_cached_power_bin(int exp, int *dec_exp) +{ + int k; + u_int index; + const nxt_cpe_t *cp; + + k = (int) ceil((-61 - exp) * NXT_D_1_LOG2_10) + + NXT_DECIMAL_EXPONENT_OFF - 1; + + index = (unsigned) (k >> 3) + 1; + + cp = &nxt_cached_powers[index]; + + *dec_exp = -(NXT_DECIMAL_EXPONENT_MIN + (int) (index << 3)); + + return nxt_diyfp(cp->significand, cp->bin_exp); +} diff --git a/nxt/nxt_diyfp.h b/nxt/nxt_diyfp.h new file mode 100644 index 00000000..ec36d2aa --- /dev/null +++ b/nxt/nxt_diyfp.h @@ -0,0 +1,212 @@ + +/* + * 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 _NXT_DIYFP_H_INCLUDED_ +#define _NXT_DIYFP_H_INCLUDED_ + +#include +#include + + +typedef struct { + uint64_t significand; + int exp; +} nxt_diyfp_t; + + +#define nxt_diyfp(_s, _e) (nxt_diyfp_t) \ + { .significand = (_s), .exp = (_e) } +#define nxt_uint64(h, l) (((uint64_t) (h) << 32) + (l)) + + +#define NXT_DBL_SIGNIFICAND_SIZE 52 +#define NXT_DBL_EXPONENT_BIAS (0x3FF + NXT_DBL_SIGNIFICAND_SIZE) +#define NXT_DBL_EXPONENT_MIN (-NXT_DBL_EXPONENT_BIAS) +#define NXT_DBL_EXPONENT_MAX (0x7FF - NXT_DBL_EXPONENT_BIAS) +#define NXT_DBL_EXPONENT_DENORMAL (-NXT_DBL_EXPONENT_BIAS + 1) + +#define NXT_DBL_SIGNIFICAND_MASK nxt_uint64(0x000FFFFF, 0xFFFFFFFF) +#define NXT_DBL_HIDDEN_BIT nxt_uint64(0x00100000, 0x00000000) +#define NXT_DBL_EXPONENT_MASK nxt_uint64(0x7FF00000, 0x00000000) + +#define NXT_DIYFP_SIGNIFICAND_SIZE 64 + +#define NXT_SIGNIFICAND_SIZE 53 +#define NXT_SIGNIFICAND_SHIFT (NXT_DIYFP_SIGNIFICAND_SIZE \ + - NXT_DBL_SIGNIFICAND_SIZE) + +#define NXT_DECIMAL_EXPONENT_OFF 348 +#define NXT_DECIMAL_EXPONENT_MIN (-348) +#define NXT_DECIMAL_EXPONENT_MAX 340 +#define NXT_DECIMAL_EXPONENT_DIST 8 + + +nxt_inline nxt_diyfp_t +nxt_d2diyfp(double d) +{ + int biased_exp; + uint64_t significand; + nxt_diyfp_t r; + + union { + double d; + uint64_t u64; + } u; + + u.d = d; + + biased_exp = (u.u64 & NXT_DBL_EXPONENT_MASK) >> NXT_DBL_SIGNIFICAND_SIZE; + significand = u.u64 & NXT_DBL_SIGNIFICAND_MASK; + + if (biased_exp != 0) { + r.significand = significand + NXT_DBL_HIDDEN_BIT; + r.exp = biased_exp - NXT_DBL_EXPONENT_BIAS; + + } else { + r.significand = significand; + r.exp = NXT_DBL_EXPONENT_MIN + 1; + } + + return r; +} + + +nxt_inline double +nxt_diyfp2d(nxt_diyfp_t v) +{ + int exp; + uint64_t significand, biased_exp; + + union { + double d; + uint64_t u64; + } u; + + exp = v.exp; + significand = v.significand; + + while (significand > NXT_DBL_HIDDEN_BIT + NXT_DBL_SIGNIFICAND_MASK) { + significand >>= 1; + exp++; + } + + if (exp >= NXT_DBL_EXPONENT_MAX) { + return INFINITY; + } + + if (exp < NXT_DBL_EXPONENT_DENORMAL) { + return 0.0; + } + + while (exp > NXT_DBL_EXPONENT_DENORMAL + && (significand & NXT_DBL_HIDDEN_BIT) == 0) + { + significand <<= 1; + exp--; + } + + if (exp == NXT_DBL_EXPONENT_DENORMAL + && (significand & NXT_DBL_HIDDEN_BIT) == 0) + { + biased_exp = 0; + + } else { + biased_exp = (uint64_t) (exp + NXT_DBL_EXPONENT_BIAS); + } + + u.u64 = (significand & NXT_DBL_SIGNIFICAND_MASK) + | (biased_exp << NXT_DBL_SIGNIFICAND_SIZE); + + return u.d; +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_shift_left(nxt_diyfp_t v, unsigned shift) +{ + return nxt_diyfp(v.significand << shift, v.exp - shift); +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_shift_right(nxt_diyfp_t v, unsigned shift) +{ + return nxt_diyfp(v.significand >> shift, v.exp + shift); +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_sub(nxt_diyfp_t lhs, nxt_diyfp_t rhs) +{ + return nxt_diyfp(lhs.significand - rhs.significand, lhs.exp); +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_mul(nxt_diyfp_t lhs, nxt_diyfp_t rhs) +{ +#if (NXT_HAVE_UNSIGNED_INT128) + + uint64_t l, h; + nxt_uint128_t u128; + + u128 = (nxt_uint128_t) (lhs.significand) + * (nxt_uint128_t) (rhs.significand); + + h = u128 >> 64; + l = (uint64_t) u128; + + /* rounding. */ + + if (l & ((uint64_t) 1 << 63)) { + h++; + } + + return nxt_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 nxt_diyfp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), + lhs.exp + rhs.exp + 64); + +#endif +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_normalize(nxt_diyfp_t v) +{ + return nxt_diyfp_shift_left(v, nxt_leading_zeros64(v.significand)); +} + + +nxt_diyfp_t nxt_cached_power_dec(int exp, int *dec_exp); +nxt_diyfp_t nxt_cached_power_bin(int exp, int *dec_exp); + + +#endif /* _NXT_DIYFP_H_INCLUDED_ */ diff --git a/nxt/nxt_dtoa.c b/nxt/nxt_dtoa.c new file mode 100644 index 00000000..5aa37cee --- /dev/null +++ b/nxt/nxt_dtoa.c @@ -0,0 +1,363 @@ + +/* + * 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. + * + * Copyright (C) 2015 Doug Currie + * based on dtoa_milo.h + * Copyright (C) 2014 Milo Yip + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + + +nxt_inline void +nxt_grisu2_round(char *start, size_t len, uint64_t delta, uint64_t rest, + uint64_t ten_kappa, uint64_t wp_w) +{ + while (rest < wp_w && delta - rest >= ten_kappa + && (rest + ten_kappa < wp_w || /* closer */ + wp_w - rest > rest + ten_kappa - wp_w)) + { + start[len - 1]--; + rest += ten_kappa; + } +} + + +nxt_inline int +nxt_dec_count(uint32_t n) +{ + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + if (n < 1000000000) return 9; + + return 10; +} + + +nxt_inline size_t +nxt_grisu2_gen(nxt_diyfp_t W, nxt_diyfp_t Mp, uint64_t delta, char *start, + int *dec_exp) +{ + int kappa; + char c, *p; + uint32_t p1, d; + uint64_t p2, tmp; + nxt_diyfp_t one, wp_w; + + static const uint64_t pow10[] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + + wp_w = nxt_diyfp_sub(Mp, W); + + one = nxt_diyfp((uint64_t) 1 << -Mp.exp, Mp.exp); + p1 = (uint32_t) (Mp.significand >> -one.exp); + p2 = Mp.significand & (one.significand - 1); + + p = start; + + kappa = nxt_dec_count(p1); + + while (kappa > 0) { + + switch (kappa) { + case 10: d = p1 / 1000000000; p1 %= 1000000000; break; + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default: + nxt_unreachable(); + } + + if (d != 0 || p != start) { + *p++ = '0' + d; + } + + kappa--; + + tmp = ((uint64_t) p1 << -one.exp) + p2; + + if (tmp <= delta) { + *dec_exp += kappa; + nxt_grisu2_round(start, p - start, delta, tmp, + pow10[kappa] << -one.exp, wp_w.significand); + return p - start; + } + } + + /* kappa = 0. */ + + for ( ;; ) { + p2 *= 10; + delta *= 10; + c = (char) (p2 >> -one.exp); + + if (c != 0 || p != start) { + *p++ = '0' + c; + } + + p2 &= one.significand - 1; + kappa--; + + if (p2 < delta) { + *dec_exp += kappa; + tmp = (-kappa < 10) ? pow10[-kappa] : 0; + nxt_grisu2_round(start, p - start, delta, p2, one.significand, + wp_w.significand * tmp); + break; + } + } + + return p - start; +} + + +nxt_inline nxt_diyfp_t +nxt_diyfp_normalize_boundary(nxt_diyfp_t v) +{ + while ((v.significand & (NXT_DBL_HIDDEN_BIT << 1)) == 0) { + v.significand <<= 1; + v.exp--; + } + + return nxt_diyfp_shift_left(v, NXT_SIGNIFICAND_SHIFT - 2); +} + + +nxt_inline void +nxt_diyfp_normalize_boundaries(nxt_diyfp_t v, nxt_diyfp_t* minus, + nxt_diyfp_t* plus) +{ + nxt_diyfp_t pl, mi; + + pl = nxt_diyfp_normalize_boundary(nxt_diyfp((v.significand << 1) + 1, + v.exp - 1)); + + if (v.significand == NXT_DBL_HIDDEN_BIT) { + mi = nxt_diyfp((v.significand << 2) - 1, v.exp - 2); + + } else { + mi = nxt_diyfp((v.significand << 1) - 1, v.exp - 1); + } + + mi.significand <<= mi.exp - pl.exp; + mi.exp = pl.exp; + + *plus = pl; + *minus = mi; +} + + +nxt_inline size_t +nxt_grisu2(double value, char *start, int *dec_exp) +{ + nxt_diyfp_t v, w_m, w_p, c_mk, W, Wp, Wm; + + v = nxt_d2diyfp(value); + + nxt_diyfp_normalize_boundaries(v, &w_m, &w_p); + + c_mk = nxt_cached_power_bin(w_p.exp, dec_exp); + W = nxt_diyfp_mul(nxt_diyfp_normalize(v), c_mk); + + Wp = nxt_diyfp_mul(w_p, c_mk); + Wm = nxt_diyfp_mul(w_m, c_mk); + + Wm.significand++; + Wp.significand--; + + return nxt_grisu2_gen(W, Wp, Wp.significand - Wm.significand, start, + dec_exp); +} + + +nxt_inline size_t +nxt_write_exponent(int exp, char* start) +{ + char *p; + size_t len; + uint32_t u32; + char buf[4]; + + /* -324 <= exp <= 308. */ + + if (exp < 0) { + *start++ = '-'; + exp = -exp; + + } else { + *start++ = '+'; + } + + u32 = exp; + p = buf + nxt_length(buf); + + do { + *--p = u32 % 10 + '0'; + u32 /= 10; + } while (u32 != 0); + + len = buf + nxt_length(buf) - p; + + memcpy(start, p, len); + + return len + 1; +} + + +nxt_inline size_t +nxt_prettify(char *start, size_t len, int dec_exp) +{ + int kk, offset, length; + size_t size; + + /* 10^(kk-1) <= v < 10^kk */ + + length = (int) len; + kk = length + dec_exp; + + if (length <= kk && kk <= 21) { + + /* 1234e7 -> 12340000000 */ + + if (kk - length > 0) { + memset(&start[length], '0', kk - length); + } + + return kk; + + } else if (0 < kk && kk <= 21) { + + /* 1234e-2 -> 12.34 */ + + memmove(&start[kk + 1], &start[kk], length - kk); + start[kk] = '.'; + + return (length + 1); + + } else if (-6 < kk && kk <= 0) { + + /* 1234e-6 -> 0.001234 */ + + offset = 2 - kk; + memmove(&start[offset], start, length); + + start[0] = '0'; + start[1] = '.'; + + if (offset - 2 > 0) { + memset(&start[2], '0', offset - 2); + } + + return (length + offset); + + } else if (length == 1) { + + /* 1e30 */ + + start[1] = 'e'; + + size = nxt_write_exponent(kk - 1, &start[2]); + + return (size + 2); + + } + + /* 1234e30 -> 1.234e33 */ + + memmove(&start[2], &start[1], length - 1); + start[1] = '.'; + start[length + 1] = 'e'; + + size = nxt_write_exponent(kk - 1, &start[length + 2]); + + return (size + length + 2); +} + + +size_t +nxt_dtoa(double value, char *start) +{ + int dec_exp, minus; + char *p; + size_t length; + + /* Not handling NaN and inf. */ + + minus = 0; + p = start; + + if (signbit(value)) { + *p++ = '-'; + value = -value; + minus = 1; + } + + if (value == 0) { + *p++ = '0'; + + return (p - start); + } + + length = nxt_grisu2(value, p, &dec_exp); + + length = nxt_prettify(p, length, dec_exp); + + return (minus + length); +} diff --git a/nxt/nxt_dtoa.h b/nxt/nxt_dtoa.h new file mode 100644 index 00000000..9f6c1e25 --- /dev/null +++ b/nxt/nxt_dtoa.h @@ -0,0 +1,12 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) Nginx, Inc. + */ + +#ifndef _NXT_DTOA_H_INCLUDED_ +#define _NXT_DTOA_H_INCLUDED_ + +NXT_EXPORT size_t nxt_dtoa(double value, char* buffer); + +#endif /* _NXT_DTOA_H_INCLUDED_ */ diff --git a/nxt/nxt_strtod.c b/nxt/nxt_strtod.c new file mode 100644 index 00000000..0052703c --- /dev/null +++ b/nxt/nxt_strtod.c @@ -0,0 +1,403 @@ +/* + * 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 +#include +#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 NXT_DECIMAL_POWER_MAX 309 +#define NXT_DECIMAL_POWER_MIN (-324) + +#define NXT_UINT64_MAX nxt_uint64(0xFFFFFFFF, 0xFFFFFFFF) +#define NXT_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. + */ +nxt_inline uint64_t +nxt_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 <= (NXT_UINT64_MAX / 10 - 1)) { + d = *p++ - '0'; + value = 10 * value + d; + } + + *ndigits = p - start; + + return value; +} + + +/* + * Reads a nxt_diyfp_t from the buffer. + * The returned nxt_diyfp_t is not necessarily normalized. + * If remaining is zero then the returned nxt_diyfp_t is accurate. + * Otherwise it has been rounded and has error of at most 1/2 ulp. + */ +static nxt_diyfp_t +nxt_diyfp_read(const u_char *start, size_t length, int *remaining) +{ + size_t read; + uint64_t significand; + + significand = nxt_read_uint64(start, length, &read); + + /* Round the significand. */ + + if (length != read) { + if (start[read] >= '5') { + significand++; + } + } + + *remaining = length - read; + + return nxt_diyfp(significand, 0); +} + + +/* + * Returns 10^exp as an exact nxt_diyfp_t. + * The given exp must be in the range [1; NXT_DECIMAL_EXPONENT_DIST[. + */ +nxt_inline nxt_diyfp_t +nxt_adjust_pow10(int exp) +{ + switch (exp) { + case 1: + return nxt_diyfp(nxt_uint64(0xa0000000, 00000000), -60); + case 2: + return nxt_diyfp(nxt_uint64(0xc8000000, 00000000), -57); + case 3: + return nxt_diyfp(nxt_uint64(0xfa000000, 00000000), -54); + case 4: + return nxt_diyfp(nxt_uint64(0x9c400000, 00000000), -50); + case 5: + return nxt_diyfp(nxt_uint64(0xc3500000, 00000000), -47); + case 6: + return nxt_diyfp(nxt_uint64(0xf4240000, 00000000), -44); + case 7: + return nxt_diyfp(nxt_uint64(0x98968000, 00000000), -40); + default: + nxt_unreachable(); + } +} + + +/* + * 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 + * NXT_SIGNIFICAND_SIZE. The only exception are denormals. They start with + * leading zeroes and their effective significand-size is hence smaller. + */ +nxt_inline int +nxt_diyfp_sgnd_size(int order) +{ + if (order >= (NXT_DBL_EXPONENT_DENORMAL + NXT_SIGNIFICAND_SIZE)) { + return NXT_SIGNIFICAND_SIZE; + } + + if (order <= NXT_DBL_EXPONENT_DENORMAL) { + return 0; + } + + return order - NXT_DBL_EXPONENT_DENORMAL; +} + + +#define NXT_DENOM_LOG 3 +#define NXT_DENOM (1 << NXT_DENOM_LOG) + +/* + * Returns either the correct double or the double that is just below + * the correct double. + */ +static double +nxt_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; + nxt_diyfp_t value, pow, adj_pow, rounded; + + value = nxt_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 : NXT_DENOM / 2); + + orig_e = value.exp; + value = nxt_diyfp_normalize(value); + error <<= orig_e - value.exp; + + if (exp < NXT_DECIMAL_EXPONENT_MIN) { + return 0.0; + } + + pow = nxt_cached_power_dec(exp, &dec_exp); + + if (dec_exp != exp) { + + adj_exp = exp - dec_exp; + adj_pow = nxt_adjust_pow10(exp - dec_exp); + value = nxt_diyfp_mul(value, adj_pow); + + if (NXT_UINT64_DECIMAL_DIGITS_MAX - (int) length < adj_exp) { + /* + * The adjustment power is exact. There is hence only + * an error of 0.5. + */ + error += NXT_DENOM / 2; + } + } + + value = nxt_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 / NXT_DENOM > error_a * error_b / 2^64. + */ + + error += NXT_DENOM + (error != 0 ? 1 : 0); + + orig_e = value.exp; + value = nxt_diyfp_normalize(value); + error <<= orig_e - value.exp; + + /* + * Check whether the double's significand changes when the error is added + * or substracted. + */ + + magnitude = NXT_DIYFP_SIGNIFICAND_SIZE + value.exp; + prec_digits = NXT_DIYFP_SIGNIFICAND_SIZE - nxt_diyfp_sgnd_size(magnitude); + + if (prec_digits + NXT_DENOM_LOG >= NXT_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 + NXT_DENOM_LOG - NXT_DIYFP_SIGNIFICAND_SIZE + 1; + + value = nxt_diyfp_shift_right(value, shift); + + /* + * Add 1 for the lost precision of error, and NXT_DENOM + * for the lost precision of value.significand. + */ + error = (error >> shift) + 1 + NXT_DENOM; + prec_digits -= shift; + } + + prec_bits = value.significand & (((uint64_t) 1 << prec_digits) - 1); + prec_bits *= NXT_DENOM; + + half_way = (uint64_t) 1 << (prec_digits - 1); + half_way *= NXT_DENOM; + + rounded = nxt_diyfp_shift_right(value, prec_digits); + + if (prec_bits >= half_way + error) { + rounded.significand++; + } + + return nxt_diyfp2d(rounded); +} + + +static double +nxt_strtod_internal(const u_char *start, size_t length, int exp) +{ + 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; + } + + exp += (int) (left - right); + + if (exp + (int) length - 1 >= NXT_DECIMAL_POWER_MAX) { + return INFINITY; + } + + if (exp + (int) length <= NXT_DECIMAL_POWER_MIN) { + return 0.0; + } + + return nxt_diyfp_strtod(start, length, exp); +} + + +double +nxt_strtod(const u_char **start, const u_char *end) +{ + int exponent, exp, insignf; + u_char c, *pos; + nxt_bool_t minus; + const u_char *e, *p, *last; + u_char data[128]; + + exponent = 0; + insignf = 0; + + pos = data; + last = data + sizeof(data); + + for (p = *start; p < end; p++) { + /* Values less than '0' become >= 208. */ + c = *p - '0'; + + if (nxt_slow_path(c > 9)) { + break; + } + + if (pos < last) { + *pos++ = *p; + + } else { + insignf++; + } + } + + /* Do not emit a '.', but adjust the exponent instead. */ + if (p < end && *p == '.') { + + for (p++; p < end; p++) { + /* Values less than '0' become >= 208. */ + c = *p - '0'; + + if (nxt_slow_path(c > 9)) { + break; + } + + if (pos < last) { + *pos++ = *p; + exponent--; + + } else { + /* Ignore insignificant digits in the fractional part. */ + } + } + } + + 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 (nxt_fast_path(c <= 9)) { + exp = c; + + for (p = e + 1; p < end; p++) { + /* Values less than '0' become >= 208. */ + c = *p - '0'; + + if (nxt_slow_path(c > 9)) { + break; + } + + exp = exp * 10 + c; + } + + exponent += minus ? -exp : exp; + } + } + + *start = p; + + exponent += insignf; + + return nxt_strtod_internal(data, pos - data, exponent); +} diff --git a/nxt/nxt_strtod.h b/nxt/nxt_strtod.h new file mode 100644 index 00000000..c36cc7b3 --- /dev/null +++ b/nxt/nxt_strtod.h @@ -0,0 +1,12 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) Nginx, Inc. + */ + +#ifndef _NXT_STRTOD_H_INCLUDED_ +#define _NXT_STRTOD_H_INCLUDED_ + +NXT_EXPORT double nxt_strtod(const u_char **start, const u_char *end); + +#endif /* _NXT_STRTOD_H_INCLUDED_ */ diff --git a/nxt/nxt_types.h b/nxt/nxt_types.h index 9fa99733..3604a8f8 100644 --- a/nxt/nxt_types.h +++ b/nxt/nxt_types.h @@ -50,6 +50,11 @@ typedef uintptr_t nxt_uint_t; #endif +#if (NXT_HAVE_UNSIGNED_INT128) +typedef unsigned __int128 nxt_uint128_t; +#endif + + typedef nxt_uint_t nxt_bool_t; -- 2.47.3