nginx / njs

A subset of JavaScript language to use in nginx
http://nginx.org/en/docs/njs/
BSD 2-Clause "Simplified" License
1.02k stars 147 forks source link

internals: add correctly implemented dtoa routine #28

Closed drsm closed 6 years ago

drsm commented 6 years ago

https://www.ecma-international.org/ecma-262/6.0/#sec-tostring-applied-to-the-number-type

some reference implementations may be found here https://github.com/miloyip/dtoa-benchmark

drsm commented 6 years ago

this one looks very nice https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftDtoa.cpp

xeioex commented 6 years ago

Code is based upon https://github.com/miloyip/dtoa-benchmark/tree/master/src/emyg and https://github.com/v8/v8/blob/master/src/strtod.cc. Please, note that the realization is not 100% accurate in comparison to V8 (Bignumbers are omited). It works well for typical values, but can differ a bit in the most pathological numbers. I think it is OK, because NJS is not for complex mathematical computations. This patch also fixes #30.

Here is the draft version of the patch:

# HG changeset patch
# User Dmitry Volyntsev <xeioex@nginx.com>
# Date 1530815219 -10800
#      Thu Jul 05 21:26:59 2018 +0300
# Node ID 30fa4787ef1b375607a9c40e2aeb7cb89b074bb0
# Parent  c1f9fe4022bc2e33b9491d528346090dfd1f481f
Fixed Number.toString().

The patch adds correct dtoa() and strtod() realization.
This fixes #28 issue on GitHub.

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,9 @@ NXT_BUILDDIR =    build
    $(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 =    build
        $(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
--- a/njs/njs_core.h
+++ b/njs/njs_core.h
@@ -15,6 +15,8 @@
 #include <nxt_string.h>
 #include <nxt_stub.h>
 #include <nxt_utf8.h>
+#include <nxt_dtoa.h>
+#include <nxt_strtod.h>
 #include <nxt_djb_hash.h>
 #include <nxt_trace.h>
 #include <nxt_array.h>
diff --git a/njs/njs_json.c b/njs/njs_json.c
--- a/njs/njs_json.c
+++ b/njs/njs_json.c
@@ -1844,7 +1844,7 @@ njs_json_append_number(njs_json_stringif
             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
--- a/njs/njs_number.c
+++ b/njs/njs_number.c
@@ -66,91 +66,7 @@ njs_value_to_index(const njs_value_t *va
 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);
 }

@@ -303,10 +219,10 @@ njs_ret_t
 njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number)
 {
+    u_char             buf[128];
     double             num;
     size_t             size;
     const njs_value_t  *value;
-    u_char             buf[128];

     num = number->data.u.number;

@@ -323,7 +239,7 @@ njs_number_to_string(njs_vm_t *vm, njs_v
         }

     } 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);
     }
diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c
+++ b/njs/test/njs_unit_test.c
@@ -96,16 +96,14 @@ static njs_unit_test_t  njs_test[] =
     { 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("18446744073709553000") },

     { nxt_string("1.7976931348623157E+308"),
       nxt_string("1.7976931348623157e+308") },
-#endif

     { nxt_string("+1"),
       nxt_string("1") },
@@ -223,16 +221,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 +627,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") },
@@ -7467,7 +7465,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") },
@@ -7726,7 +7724,7 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("-Infinity") },

     { nxt_string("Math.cbrt('27')"),
-      nxt_string("3") },
+      nxt_string("3.0000000000000006") },

     { nxt_string("Math.cbrt(-1)"),
       nxt_string("-1") },
@@ -8507,10 +8505,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") },
@@ -8519,7 +8517,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") },
diff --git a/nxt/Makefile b/nxt/Makefile
--- 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_LIB = nxt
    $(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_LIB =    nxt
        $(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
--- a/nxt/auto/clang
+++ b/nxt/auto/clang
@@ -217,6 +217,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_diyfp.c b/nxt/nxt_diyfp.c
new file mode 100644
--- /dev/null
+++ b/nxt/nxt_diyfp.c
@@ -0,0 +1,153 @@
+
+/*
+ * An internal nxt_diyfp_t implementation based on V8 src/cached-powers.cc.
+ *
+ * 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 <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_diyfp.h>
+
+#include <assert.h>
+
+
+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[] = {
+  {UINT64_C2(0xfa8fd5a0, 0x081c0288), -1220, -348},
+  {UINT64_C2(0xbaaee17f, 0xa23ebf76), -1193, -340},
+  {UINT64_C2(0x8b16fb20, 0x3055ac76), -1166, -332},
+  {UINT64_C2(0xcf42894a, 0x5dce35ea), -1140, -324},
+  {UINT64_C2(0x9a6bb0aa, 0x55653b2d), -1113, -316},
+  {UINT64_C2(0xe61acf03, 0x3d1a45df), -1087, -308},
+  {UINT64_C2(0xab70fe17, 0xc79ac6ca), -1060, -300},
+  {UINT64_C2(0xff77b1fc, 0xbebcdc4f), -1034, -292},
+  {UINT64_C2(0xbe5691ef, 0x416bd60c), -1007, -284},
+  {UINT64_C2(0x8dd01fad, 0x907ffc3c),  -980, -276},
+  {UINT64_C2(0xd3515c28, 0x31559a83),  -954, -268},
+  {UINT64_C2(0x9d71ac8f, 0xada6c9b5),  -927, -260},
+  {UINT64_C2(0xea9c2277, 0x23ee8bcb),  -901, -252},
+  {UINT64_C2(0xaecc4991, 0x4078536d),  -874, -244},
+  {UINT64_C2(0x823c1279, 0x5db6ce57),  -847, -236},
+  {UINT64_C2(0xc2109436, 0x4dfb5637),  -821, -228},
+  {UINT64_C2(0x9096ea6f, 0x3848984f),  -794, -220},
+  {UINT64_C2(0xd77485cb, 0x25823ac7),  -768, -212},
+  {UINT64_C2(0xa086cfcd, 0x97bf97f4),  -741, -204},
+  {UINT64_C2(0xef340a98, 0x172aace5),  -715, -196},
+  {UINT64_C2(0xb23867fb, 0x2a35b28e),  -688, -188},
+  {UINT64_C2(0x84c8d4df, 0xd2c63f3b),  -661, -180},
+  {UINT64_C2(0xc5dd4427, 0x1ad3cdba),  -635, -172},
+  {UINT64_C2(0x936b9fce, 0xbb25c996),  -608, -164},
+  {UINT64_C2(0xdbac6c24, 0x7d62a584),  -582, -156},
+  {UINT64_C2(0xa3ab6658, 0x0d5fdaf6),  -555, -148},
+  {UINT64_C2(0xf3e2f893, 0xdec3f126),  -529, -140},
+  {UINT64_C2(0xb5b5ada8, 0xaaff80b8),  -502, -132},
+  {UINT64_C2(0x87625f05, 0x6c7c4a8b),  -475, -124},
+  {UINT64_C2(0xc9bcff60, 0x34c13053),  -449, -116},
+  {UINT64_C2(0x964e858c, 0x91ba2655),  -422, -108},
+  {UINT64_C2(0xdff97724, 0x70297ebd),  -396, -100},
+  {UINT64_C2(0xa6dfbd9f, 0xb8e5b88f),  -369,  -92},
+  {UINT64_C2(0xf8a95fcf, 0x88747d94),  -343,  -84},
+  {UINT64_C2(0xb9447093, 0x8fa89bcf),  -316,  -76},
+  {UINT64_C2(0x8a08f0f8, 0xbf0f156b),  -289,  -68},
+  {UINT64_C2(0xcdb02555, 0x653131b6),  -263,  -60},
+  {UINT64_C2(0x993fe2c6, 0xd07b7fac),  -236,  -52},
+  {UINT64_C2(0xe45c10c4, 0x2a2b3b06),  -210,  -44},
+  {UINT64_C2(0xaa242499, 0x697392d3),  -183,  -36},
+  {UINT64_C2(0xfd87b5f2, 0x8300ca0e),  -157,  -28},
+  {UINT64_C2(0xbce50864, 0x92111aeb),  -130,  -20},
+  {UINT64_C2(0x8cbccc09, 0x6f5088cc),  -103,  -12},
+  {UINT64_C2(0xd1b71758, 0xe219652c),   -77,   -4},
+  {UINT64_C2(0x9c400000, 0x00000000),   -50,    4},
+  {UINT64_C2(0xe8d4a510, 0x00000000),   -24,   12},
+  {UINT64_C2(0xad78ebc5, 0xac620000),     3,   20},
+  {UINT64_C2(0x813f3978, 0xf8940984),    30,   28},
+  {UINT64_C2(0xc097ce7b, 0xc90715b3),    56,   36},
+  {UINT64_C2(0x8f7e32ce, 0x7bea5c70),    83,   44},
+  {UINT64_C2(0xd5d238a4, 0xabe98068),   109,   52},
+  {UINT64_C2(0x9f4f2726, 0x179a2245),   136,   60},
+  {UINT64_C2(0xed63a231, 0xd4c4fb27),   162,   68},
+  {UINT64_C2(0xb0de6538, 0x8cc8ada8),   189,   76},
+  {UINT64_C2(0x83c7088e, 0x1aab65db),   216,   84},
+  {UINT64_C2(0xc45d1df9, 0x42711d9a),   242,   92},
+  {UINT64_C2(0x924d692c, 0xa61be758),   269,  100},
+  {UINT64_C2(0xda01ee64, 0x1a708dea),   295,  108},
+  {UINT64_C2(0xa26da399, 0x9aef774a),   322,  116},
+  {UINT64_C2(0xf209787b, 0xb47d6b85),   348,  124},
+  {UINT64_C2(0xb454e4a1, 0x79dd1877),   375,  132},
+  {UINT64_C2(0x865b8692, 0x5b9bc5c2),   402,  140},
+  {UINT64_C2(0xc83553c5, 0xc8965d3d),   428,  148},
+  {UINT64_C2(0x952ab45c, 0xfa97a0b3),   455,  156},
+  {UINT64_C2(0xde469fbd, 0x99a05fe3),   481,  164},
+  {UINT64_C2(0xa59bc234, 0xdb398c25),   508,  172},
+  {UINT64_C2(0xf6c69a72, 0xa3989f5c),   534,  180},
+  {UINT64_C2(0xb7dcbf53, 0x54e9bece),   561,  188},
+  {UINT64_C2(0x88fcf317, 0xf22241e2),   588,  196},
+  {UINT64_C2(0xcc20ce9b, 0xd35c78a5),   614,  204},
+  {UINT64_C2(0x98165af3, 0x7b2153df),   641,  212},
+  {UINT64_C2(0xe2a0b5dc, 0x971f303a),   667,  220},
+  {UINT64_C2(0xa8d9d153, 0x5ce3b396),   694,  228},
+  {UINT64_C2(0xfb9b7cd9, 0xa4a7443c),   720,  236},
+  {UINT64_C2(0xbb764c4c, 0xa7a44410),   747,  244},
+  {UINT64_C2(0x8bab8eef, 0xb6409c1a),   774,  252},
+  {UINT64_C2(0xd01fef10, 0xa657842c),   800,  260},
+  {UINT64_C2(0x9b10a4e5, 0xe9913129),   827,  268},
+  {UINT64_C2(0xe7109bfb, 0xa19c0c9d),   853,  276},
+  {UINT64_C2(0xac2820d9, 0x623bf429),   880,  284},
+  {UINT64_C2(0x80444b5e, 0x7aa7cf85),   907,  292},
+  {UINT64_C2(0xbf21e440, 0x03acdd2d),   933,  300},
+  {UINT64_C2(0x8e679c2f, 0x5e44ff8f),   960,  308},
+  {UINT64_C2(0xd433179d, 0x9c8cb841),   986,  316},
+  {UINT64_C2(0x9e19db92, 0xb4e31ba9),  1013,  324},
+  {UINT64_C2(0xeb96bf6e, 0xbadf77d9),  1039,  332},
+  {UINT64_C2(0xaf87023b, 0x9bf0ee6b),  1066,  340},
+};
+
+
+nxt_diyfp_t
+nxt_cached_power_dec(int exp, int* K)
+{
+    assert(NXT_DECIMAL_EXPONENT_MIN <= exp);
+    assert(exp < NXT_DECIMAL_EXPONENT_MAX + NXT_DECIMAL_EXPONENT_DIST);
+
+    int index = (exp + NXT_DECIMAL_EXPONENT_OFF) / NXT_DECIMAL_EXPONENT_DIST;
+    nxt_cpe_t cp = nxt_cached_powers[index];
+
+    *K = cp.dec_exp;
+
+    assert(*K <= exp);
+    assert(exp < *K + NXT_DECIMAL_EXPONENT_DIST);
+
+    return nxt_diyfp(cp.significand, cp.bin_exp);
+}
+
+
+nxt_diyfp_t
+nxt_cached_power_bin(int exp, int* K)
+{
+   //int k = (int )(ceil((-61 - e) * 0.30102999566398114)) + 374;
+    //dk must be positive, so can do ceiling in positive
+   double dk = (-61 - exp) * 0.30102999566398114 + 347;
+   int k = (int) dk;
+    if (k != dk)
+        k++;
+
+   unsigned index = (unsigned )((k >> 3) + 1);
+
+   assert(index < sizeof(nxt_cached_powers) / sizeof(nxt_cached_powers[0]));
+
+    nxt_cpe_t cp = nxt_cached_powers[index];
+
+   *K = -(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
--- /dev/null
+++ b/nxt/nxt_diyfp.h
@@ -0,0 +1,246 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#ifndef _NXT_DIYFP_H_INCLUDED_
+#define _NXT_DIYFP_H_INCLUDED_
+
+#include <nxt_types.h>
+#include <math.h>
+
+typedef struct {
+    uint64_t    f;
+    int         e;
+} nxt_diyfp_t;
+
+
+#define UINT64_C2(h, l)             (((uint64_t) (h) << 32) | (uint64_t) (l))
+
+#define nxt_diyfp(_f, _e)           (nxt_diyfp_t) {.f = (_f), .e = (_e)}
+
+#define NXT_DIYFP_SIGNIFICAND_SIZE  64
+#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_SIGNIFICAND_MASK    UINT64_C2(0x000FFFFF, 0xFFFFFFFF)
+
+#define kDpExponentMask             UINT64_C2(0x7FF00000, 0x00000000)
+#define kDpSignificandMask          UINT64_C2(0x000FFFFF, 0xFFFFFFFF)
+#define kDpHiddenBit                UINT64_C2(0x00100000, 0x00000000)
+
+#define kSignificandSize            53
+#define kDenormalExponent           (-NXT_DBL_EXPONENT_BIAS + 1)
+
+
+#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_diyfp_t nxt_cached_power_dec(int exponent, int* K);
+nxt_diyfp_t nxt_cached_power_bin(int e, int* K);
+
+
+nxt_inline nxt_diyfp_t
+nxt_d2diyfp(double d)
+{
+    int          biased_e;
+    uint64_t     significand;
+    nxt_diyfp_t  r;
+
+    union {
+        double   d;
+        uint64_t u64;
+    } u = { d };
+
+    biased_e = (u.u64 & kDpExponentMask) >> NXT_DBL_SIGNIFICAND_SIZE;
+    significand = u.u64 & NXT_DBL_SIGNIFICAND_MASK;
+
+    if (biased_e != 0) {
+        r.f = significand + kDpHiddenBit;
+        r.e = biased_e - NXT_DBL_EXPONENT_BIAS;
+
+    } else {
+        r.f = significand;
+        r.e = 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.e;
+    significand = v.f;
+
+    while (significand > kDpHiddenBit + NXT_DBL_SIGNIFICAND_MASK) {
+        significand >>= 1;
+        exp++;
+    }
+
+    if (exp >= NXT_DBL_EXPONENT_MAX) {
+        return INFINITY;
+    }
+
+    if (exp < kDenormalExponent) {
+        return 0.0;
+    }
+
+    while (exp > kDenormalExponent && (significand & kDpHiddenBit) == 0) {
+        significand <<= 1;
+        exp--;
+    }
+
+    if (exp == kDenormalExponent && (significand & kDpHiddenBit) == 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_subtract(nxt_diyfp_t lhs, nxt_diyfp_t rhs)
+{
+    return nxt_diyfp(lhs.f - rhs.f, lhs.e);
+}
+
+
+nxt_inline nxt_diyfp_t
+nxt_diyfp_multiply(nxt_diyfp_t lhs, nxt_diyfp_t rhs)
+{
+#if ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) \
+       || __clang_major__ >= 9) && defined(__x86_64__))
+
+    unsigned __int128 p = (unsigned __int128) (lhs.f)
+                          * (unsigned __int128) (rhs.f);
+    uint64_t h = p >> 64;
+    uint64_t l = (uint64_t) p;
+
+    if (l & ((uint64_t) 1u << 63)) /* rounding */
+        h++;
+
+    return nxt_diyfp(h, lhs.e + rhs.e + 64);
+
+#else
+
+    const uint64_t M32 = 0xFFFFFFFF;
+    const uint64_t a = lhs.f >> 32;
+    const uint64_t b = lhs.f & M32;
+    const uint64_t c = rhs.f >> 32;
+    const uint64_t d = rhs.f & M32;
+    const uint64_t ac = a * c;
+    const uint64_t bc = b * c;
+    const uint64_t ad = a * d;
+    const uint64_t bd = b * d;
+
+    uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32);
+
+    tmp += 1U << 31;  /* mult_round */
+
+    return nxt_diyfp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32),
+                            lhs.e + rhs.e + 64);
+
+#endif
+}
+
+
+nxt_inline nxt_diyfp_t
+nxt_diyfp_normalize(nxt_diyfp_t lhs)
+{
+#if defined(NXT_HAVE_BUILTIN_CLZLL)
+
+    int s = __builtin_clzll(lhs.f);
+    return nxt_diyfp(lhs.f << s, lhs.e - s);
+
+#else
+
+    nxt_diyfp_t res = lhs;
+    while (!(res.f & kDpHiddenBit)) {
+        res.f <<= 1;
+        res.e--;
+    }
+
+    res.f <<= (NXT_DIYFP_SIGNIFICAND_SIZE - NXT_DBL_SIGNIFICAND_SIZE - 1);
+    res.e = res.e - (NXT_DIYFP_SIGNIFICAND_SIZE - NXT_DBL_SIGNIFICAND_SIZE - 1);
+
+    return res;
+
+#endif
+}
+
+
+nxt_inline nxt_diyfp_t
+nxt_diyfp_normalize_boundary(nxt_diyfp_t lhs)
+{
+    nxt_diyfp_t res = lhs;
+    while (!(res.f & (kDpHiddenBit << 1))) {
+        res.f <<= 1;
+        res.e--;
+    }
+
+    res.f <<= (NXT_DIYFP_SIGNIFICAND_SIZE - NXT_DBL_SIGNIFICAND_SIZE - 2);
+    res.e = res.e - (NXT_DIYFP_SIGNIFICAND_SIZE - NXT_DBL_SIGNIFICAND_SIZE - 2);
+
+    return res;
+}
+
+
+nxt_inline void
+nxt_diyfp_normalize_boundaries(nxt_diyfp_t lhs, nxt_diyfp_t* minus,
+    nxt_diyfp_t* plus)
+{
+    nxt_diyfp_t pl = nxt_diyfp_normalize_boundary(nxt_diyfp((lhs.f << 1) + 1,
+                                                       lhs.e - 1));
+    nxt_diyfp_t mi = (lhs.f == kDpHiddenBit)
+                ? nxt_diyfp((lhs.f << 2) - 1, lhs.e - 2)
+                : nxt_diyfp((lhs.f << 1) - 1, lhs.e - 1);
+
+    mi.f <<= mi.e - pl.e;
+    mi.e = pl.e;
+
+    *plus = pl;
+    *minus = mi;
+}
+
+// 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
+// kSignificandSize. The only exception are denormals. They start with leading
+// zeroes and their effective significand-size is hence smaller.
+static inline int
+SignificandSizeForOrderOfMagnitude(int order) {
+    if (order >= (kDenormalExponent + kSignificandSize)) {
+        return kSignificandSize;
+    }
+
+    if (order <= kDenormalExponent) {
+        return 0;
+    }
+
+    return order - kDenormalExponent;
+}
+
+#endif /* _NXT_DIYFP_H_INCLUDED_ */
diff --git a/nxt/nxt_dtoa.c b/nxt/nxt_dtoa.c
new file mode 100644
--- /dev/null
+++ b/nxt/nxt_dtoa.c
@@ -0,0 +1,308 @@
+
+/*
+ * An internal dtoa() implementation based on the work of Milo Yip and Doug
+ * Currie.
+ *
+ * This code is a mostly mechanical translation of Milo Yip's C++ version of
+ * Grisu2 to C.  For algorithm information, see Loitsch, Florian. "Printing
+ * floating-point numbers quickly and accurately with integers." ACM Sigplan
+ * Notices 45.6 (2010): 233-243.
+ *
+ * emyg_dtoa.c
+ * 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 <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_diyfp.h>
+#include <nxt_dtoa.h>
+
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdint.h>
+
+
+static inline void
+GrisuRound(char* buffer, int 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))
+    {
+        buffer[len - 1]--;
+        rest += ten_kappa;
+    }
+}
+
+
+static inline unsigned
+CountDecimalDigit32(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;
+}
+
+
+static inline void
+DigitGen(const nxt_diyfp_t W, const nxt_diyfp_t Mp, uint64_t delta, char* buffer, int* len,
+    int* K)
+{
+    const nxt_diyfp_t one = nxt_diyfp((uint64_t) (1) << -Mp.e, Mp.e);
+    const nxt_diyfp_t wp_w = nxt_diyfp_subtract(Mp, W);
+    uint32_t p1 = (uint32_t) (Mp.f >> -one.e);
+    uint64_t p2 = Mp.f & (one.f - 1);
+    int kappa = (int) (CountDecimalDigit32(p1));
+
+    static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
+                                       10000000, 100000000, 1000000000 };
+    *len = 0;
+
+    while (kappa > 0) {
+        uint32_t d;
+        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 || *len)
+            buffer[(*len)++] = '0' + (char) (d);
+
+        kappa--;
+
+        uint64_t tmp = ((uint64_t) (p1) << -one.e) + p2;
+
+        if (tmp <= delta) {
+            *K += kappa;
+            GrisuRound(buffer, *len, delta, tmp,
+                       (uint64_t) (kPow10[kappa]) << -one.e, wp_w.f);
+            return;
+        }
+    }
+
+    /* kappa = 0 */
+    for (;;) {
+        p2 *= 10;
+        delta *= 10;
+        char d = (char) (p2 >> -one.e);
+
+        if (d || *len)
+            buffer[(*len)++] = '0' + d;
+
+        p2 &= one.f - 1;
+        kappa--;
+
+        if (p2 < delta) {
+            *K += kappa;
+            uint32_t kpow = (-kappa < 9) ? kPow10[-kappa] : 0;
+            GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * kpow);
+            return;
+        }
+    }
+}
+
+
+static inline void
+Grisu2(double value, char* buffer, int* length, int* K)
+{
+    const nxt_diyfp_t v = nxt_d2diyfp(value);
+    nxt_diyfp_t w_m, w_p;
+
+    nxt_diyfp_normalize_boundaries(v, &w_m, &w_p);
+
+    const nxt_diyfp_t c_mk = nxt_cached_power_bin(w_p.e, K);
+    const nxt_diyfp_t W = nxt_diyfp_multiply(nxt_diyfp_normalize(v), c_mk);
+    nxt_diyfp_t Wp = nxt_diyfp_multiply(w_p, c_mk);
+    nxt_diyfp_t Wm = nxt_diyfp_multiply(w_m, c_mk);
+    Wm.f++;
+    Wp.f--;
+
+    DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
+}
+
+
+static const char cDigitsLut[200] = {
+    '0', '0', '0', '1', '0', '2', '0', '3', '0', '4',
+    '0', '5', '0', '6', '0', '7', '0', '8', '0', '9',
+    '1', '0', '1', '1', '1', '2', '1', '3', '1', '4',
+    '1', '5', '1', '6', '1', '7', '1', '8', '1', '9',
+    '2', '0', '2', '1', '2', '2', '2', '3', '2', '4',
+    '2', '5', '2', '6', '2', '7', '2', '8', '2', '9',
+    '3', '0', '3', '1', '3', '2', '3', '3', '3', '4',
+    '3', '5', '3', '6', '3', '7', '3', '8', '3', '9',
+    '4', '0', '4', '1', '4', '2', '4', '3', '4', '4',
+    '4', '5', '4', '6', '4', '7', '4', '8', '4', '9',
+    '5', '0', '5', '1', '5', '2', '5', '3', '5', '4',
+    '5', '5', '5', '6', '5', '7', '5', '8', '5', '9',
+    '6', '0', '6', '1', '6', '2', '6', '3', '6', '4',
+    '6', '5', '6', '6', '6', '7', '6', '8', '6', '9',
+    '7', '0', '7', '1', '7', '2', '7', '3', '7', '4',
+    '7', '5', '7', '6', '7', '7', '7', '8', '7', '9',
+    '8', '0', '8', '1', '8', '2', '8', '3', '8', '4',
+    '8', '5', '8', '6', '8', '7', '8', '8', '8', '9',
+    '9', '0', '9', '1', '9', '2', '9', '3', '9', '4',
+    '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'
+};
+
+
+static inline void
+WriteExponent(int K, char* buffer)
+{
+    if (K < 0) {
+        *buffer++ = '-';
+        K = -K;
+    } else {
+        *buffer++ = '+';
+    }
+
+    if (K >= 100) {
+        *buffer++ = '0' + (char)(K / 100);
+        K %= 100;
+
+        const char* d = &cDigitsLut[K * 2];
+        *buffer++ = d[0];
+        *buffer++ = d[1];
+
+    } else if (K >= 10) {
+        const char* d = &cDigitsLut[K * 2];
+        *buffer++ = d[0];
+        *buffer++ = d[1];
+
+    } else {
+        *buffer++ = '0' + (char) (K);
+    }
+
+    *buffer = '\0';
+}
+
+
+static inline void
+Prettify(char* buffer, int length, int k)
+{
+    /* 10^(kk-1) <= v < 10^kk */
+    const int kk = length + k;
+
+    if (length <= kk && kk <= 21) {
+
+        /* 1234e7 -> 12340000000 */
+
+        for (int i = length; i < kk; i++)
+            buffer[i] = '0';
+
+        buffer[kk] = '\0';
+
+    } else if (0 < kk && kk <= 21) {
+
+        /* 1234e-2 -> 12.34 */
+
+        memmove(&buffer[kk + 1], &buffer[kk], length - kk);
+        buffer[kk] = '.';
+        buffer[length + 1] = '\0';
+
+    } else if (-6 < kk && kk <= 0) {
+
+        /* 1234e-6 -> 0.001234 */
+
+        const int offset = 2 - kk;
+        memmove(&buffer[offset], &buffer[0], length);
+
+        buffer[0] = '0';
+        buffer[1] = '.';
+
+        for (int i = 2; i < offset; i++)
+            buffer[i] = '0';
+
+        buffer[length + offset] = '\0';
+
+    } else if (length == 1) {
+
+        /* 1e30 */
+
+        buffer[1] = 'e';
+        WriteExponent(kk - 1, &buffer[2]);
+
+    } else {
+
+        /* 1234e30 -> 1.234e33 */
+
+        memmove(&buffer[2], &buffer[1], length - 1);
+        buffer[1] = '.';
+        buffer[length + 1] = 'e';
+        WriteExponent(kk - 1, &buffer[0 + length + 2]);
+    }
+}
+
+
+size_t
+nxt_dtoa(double value, char* buf)
+{
+    int   length, K;
+    char  *p;
+
+    /* Not handling NaN and inf */
+
+    assert(!isnan(value));
+    assert(!isinf(value));
+
+    p = buf;
+
+    if (signbit(value)) {
+        *p++ = '-';
+        value = -value;
+    }
+
+    if (value == 0) {
+        *p++ = '0';
+
+        return p - buf;
+
+    } else {
+
+        Grisu2(value, p, &length, &K);
+        Prettify(p, length, K);
+
+        return strlen(buf);
+    }
+}
diff --git a/nxt/nxt_dtoa.h b/nxt/nxt_dtoa.h
new file mode 100644
--- /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
--- /dev/null
+++ b/nxt/nxt_strtod.c
@@ -0,0 +1,382 @@
+/*
+ * An internal strtod() implementation based on V8 src/strtod.cc code 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 <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_diyfp.h>
+#include <nxt_strtod.h>
+#include <nxt_string.h>
+
+#include <assert.h>
+#include <math.h>
+
+/*
+ * Max double: 1.7976931348623157 x 10^308
+ * Min non-zero double: 4.9406564584124654 x 10^-324
+ * Any x >= 10^309 is interpreted as +infinity.
+ * Any x <= 10^-324 is interpreted as 0.
+ * Note that 2.5e-324 (despite being smaller than the min double) will be read
+ * as non-zero (equal to the min non-zero double).
+ */
+
+#define NXT_DECIMAL_POWER_MAX           309
+#define NXT_DECIMAL_POWER_MIN           (-324)
+
+#define NXT_UINT64_MAX                  0xFFFFFFFFFFFFFFFF
+#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 nxt_str_t *str, size_t *ndigits)
+{
+    u_char    *p, *e, d;
+    uint64_t  value;
+
+    value = 0;
+
+    p = str->start;
+    e = p + str->length;
+
+    while (p < e && value <= (NXT_UINT64_MAX / 10 - 1)) {
+        d = *p++ - '0';
+        value = 10 * value + d;
+    }
+
+    *ndigits = p - str->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 nxt_str_t *str, int *remaining)
+{
+    size_t    read;
+    uint64_t  significand;
+
+    significand = nxt_read_uint64(str, &read);
+
+    /* Round the significand. */
+
+    if (str->length != read) {
+        if (str->start[read] >= '5') {
+            significand++;
+        }
+    }
+
+    *remaining = str->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[.
+ */
+static nxt_diyfp_t
+nxt_adjust_pow10(int exp)
+{
+    assert(0 < exp);
+
+    switch (exp) {
+    case 1: return nxt_diyfp(UINT64_C2(0xa0000000, 00000000), -60);
+    case 2: return nxt_diyfp(UINT64_C2(0xc8000000, 00000000), -57);
+    case 3: return nxt_diyfp(UINT64_C2(0xfa000000, 00000000), -54);
+    case 4: return nxt_diyfp(UINT64_C2(0x9c400000, 00000000), -50);
+    case 5: return nxt_diyfp(UINT64_C2(0xc3500000, 00000000), -47);
+    case 6: return nxt_diyfp(UINT64_C2(0xf4240000, 00000000), -44);
+    case 7: return nxt_diyfp(UINT64_C2(0x98968000, 00000000), -40);
+    default:
+            nxt_unreachable();
+    }
+}
+
+
+/*
+ * If the function returns true then the result is the correct double.
+ * Otherwise it is either the correct double or the double that is just below
+ * the correct double.
+ */
+static double
+nxt_diyfp_strtod(nxt_str_t *str, int exp)
+{
+    int          remaining, dec_exp, adj_exp;
+    nxt_diyfp_t  value, pow, adj_pow, rounded;
+
+    value = nxt_diyfp_read(str, &remaining);
+
+    // Since we may have dropped some digits the value is not accurate.
+    // If remaining is different than 0 than the error is at most
+    // .5 ulp (unit in the last place).
+    // We don't want to deal with fractions and therefore keep a common
+    // denominator.
+    const int kDenominatorLog = 3;
+    const int kDenominator = 1 << kDenominatorLog;
+    // Move the remaining decimals into the exp.
+    exp += remaining;
+    int64_t error = (remaining == 0 ? 0 : kDenominator / 2);
+
+    int old_e = value.e;
+    value = nxt_diyfp_normalize(value);
+    error <<= old_e - value.e;
+
+    assert(exp <= NXT_DECIMAL_EXPONENT_MAX);
+
+    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_multiply(value, adj_pow);
+        if (NXT_UINT64_DECIMAL_DIGITS_MAX - (int) str->length < adj_exp) {
+            // The adjustment power is exact. There is hence only
+            // an error of 0.5.
+            error += kDenominator / 2;
+        }
+    }
+
+    value = nxt_diyfp_multiply(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' we have
+    //   error_b = 0.5  (all cached powers have an error of less than 0.5 ulp),
+    //   error_ab = 0 or 1 / kDenominator > error_a * error_b / 2^64
+    int error_b = kDenominator / 2;
+    int error_ab = (error == 0 ? 0 : 1);  // We round up to 1.
+    int fixed_error = kDenominator / 2;
+    error += error_b + error_ab + fixed_error;
+
+    old_e = value.e;
+    value = nxt_diyfp_normalize(value);
+    error <<= old_e - value.e;
+
+    // See if the double's significand changes if we add/subtract the error.
+    int order_of_magnitude = NXT_DIYFP_SIGNIFICAND_SIZE + value.e;
+
+    int effective_significand_size =
+        SignificandSizeForOrderOfMagnitude(order_of_magnitude);
+
+    int precision_digits_count =
+        NXT_DIYFP_SIGNIFICAND_SIZE - effective_significand_size;
+
+    if (precision_digits_count + kDenominatorLog >= 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 an uint64.
+        // Simply shift everything to the right.
+        int shift_amount = (precision_digits_count + kDenominatorLog)
+                           - NXT_DIYFP_SIGNIFICAND_SIZE + 1;
+
+        value = nxt_diyfp(value.f >> shift_amount, value.e + shift_amount);
+        // We add 1 for the lost precision of error, and kDenominator for
+        // the lost precision of value.f.
+        error = (error >> shift_amount) + 1 + kDenominator;
+        precision_digits_count -= shift_amount;
+    }
+
+    // We use uint64_ts now. This only works if the nxt_diyfp_t uses uint64_ts too.
+    assert(precision_digits_count < 64);
+
+    uint64_t one64 = 1;
+    uint64_t precision_bits_mask = (one64 << precision_digits_count) - 1;
+    uint64_t precision_bits = value.f & precision_bits_mask;
+    uint64_t half_way = one64 << (precision_digits_count - 1);
+    precision_bits *= kDenominator;
+    half_way *= kDenominator;
+
+    rounded = nxt_diyfp(value.f >> precision_digits_count,
+                        value.e + precision_digits_count);
+
+    if (precision_bits >= half_way + error) {
+        rounded.f++;
+    }
+
+    return nxt_diyfp2d(rounded);
+}
+
+
+nxt_inline size_t
+nxt_trim_leading_zeros(nxt_str_t *str)
+{
+    u_char  *p, *e;
+
+    p = str->start;
+    e = p + str->length;
+
+    while (p < e) {
+        if (*p != '0') {
+            str->start = p;
+            break;
+        }
+
+        p++;
+    }
+
+    str->length = e - p;
+
+    return str->length;
+}
+
+
+nxt_inline size_t
+nxt_trim_trailing_zeros(nxt_str_t *str)
+{
+    u_char  *b, *p;
+
+    b = str->start;
+    p = b + str->length;
+
+    while (p >= b) {
+        if (*p != '0') {
+            break;
+        }
+
+        p++;
+    }
+
+    str->length = p - b;
+
+    return str->length;
+}
+
+
+static double
+nxt_strtod_internal(nxt_str_t *str, int exp)
+{
+    size_t  left, right;
+
+    left = nxt_trim_leading_zeros(str);
+    right = nxt_trim_trailing_zeros(str);
+
+    exp += (int) (left - right);
+
+    if (str->length == 0) {
+        return 0.0;
+    }
+
+    if (exp + (int) str->length - 1 >= NXT_DECIMAL_POWER_MAX) {
+        return INFINITY;
+    }
+
+    if (exp + (int) str->length <= NXT_DECIMAL_POWER_MIN) {
+        return 0.0;
+    }
+
+    return nxt_diyfp_strtod(str, exp);
+}
+
+
+double
+nxt_strtod(const u_char **start, const u_char *end)
+{
+    int           exponent, exp;
+    u_char        c;
+    nxt_str_t     buf;
+    nxt_bool_t    minus;
+    const u_char  *e, *p;
+    u_char        data[128];
+
+    exponent = 0;
+
+    buf.start = data;
+    buf.length = 0;
+
+    p = *start;
+
+    for (; p < end && buf.length < sizeof(data); p++) {
+        /* Values less than '0' become >= 208. */
+        c = *p - '0';
+
+        if (nxt_slow_path(c > 9)) {
+            break;
+        }
+
+        buf.start[buf.length++] = *p;
+    }
+
+    /* We don't emit a '.', but adjust the exponent instead. */
+    if (p < end && *p == '.') {
+
+        p++;
+
+        for (; p < end && buf.length < sizeof(data); p++) {
+            /* Values less than '0' become >= 208. */
+            c = *p - '0';
+
+            if (nxt_slow_path(c > 9)) {
+                break;
+            }
+
+            buf.start[buf.length++] = *p;
+            exponent--;
+        }
+    }
+
+    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;
+            p = e + 1;
+
+            while (p < end) {
+                /* Values less than '0' become >= 208. */
+                c = *p - '0';
+
+                if (nxt_slow_path(c > 9)) {
+                    break;
+                }
+
+                exp = exp * 10 + c;
+                p++;
+            }
+
+            exponent += minus ? -exp : exp;
+        }
+    }
+
+    *start = p;
+
+    return nxt_strtod_internal(&buf, exponent);
+}
diff --git a/nxt/nxt_strtod.h b/nxt/nxt_strtod.h
new file mode 100644
--- /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_ */
drsm commented 6 years ago

Hi @xeioex ! gcc complains about

        nxt/nxt_strtod.c
nxt/nxt_strtod.c: In function ‘TrimTrailingZeros’:
nxt/nxt_strtod.c:62:39: error: comparison of unsigned expression >= 0 is always true [-Werror=type-limits]
         for (i = buffer.length - 1; i >= 0; --i) {

so i change the test to != 0 and found regression:

>> 0.00000123
0.00000123
>> 0.000001234
0.000001234
>> 0.0000012345
0.0000012345
>> 0.00000123456
0.00000123456
>> 0.000001234567
0.000001234567
>> 0.00000001
0
>> 0.0000001
0
>> 0.000001
0
>> 0.00001
0
>> 0.0001
0
>> 0.001
0
>> 0.01
0
>> 0.1
0
>> 
xeioex commented 6 years ago

@drsm Please, try the updated patch from my first message. That part was rewritten. Your tests work the same way as in V8.

drsm commented 6 years ago

@xeioex thank you for the patch, works fine for me.