[njs] Fixed Math.round() according to the specification.

Dmitry Volyntsev xeioex at nginx.com
Mon Nov 11 17:24:01 UTC 2019


details:   https://hg.nginx.org/njs/rev/fdd56914c9ca
branches:  
changeset: 1232:fdd56914c9ca
user:      Artem S. Povalyukhin <artem.povaluhin at gmail.com>
date:      Sun Nov 10 01:35:02 2019 +0300
description:
Fixed Math.round() according to the specification.

This closes #246 issue on Github.

diffstat:

 src/njs_diyfp.h          |  31 ++++++++++++++++++-------------
 src/njs_main.h           |   1 +
 src/njs_math.c           |  47 ++++++++++++++++++++++++++++++++++++++++++++---
 src/njs_number.c         |   1 -
 src/test/njs_unit_test.c |  18 ++++++++++++++++++
 5 files changed, 81 insertions(+), 17 deletions(-)

diffs (193 lines):

diff -r 0d119e8ce71a -r fdd56914c9ca src/njs_diyfp.h
--- a/src/njs_diyfp.h	Thu Oct 31 22:18:41 2019 +0300
+++ b/src/njs_diyfp.h	Sun Nov 10 01:35:02 2019 +0300
@@ -18,25 +18,34 @@ typedef struct {
 } 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_BIAS       (0x3FF + NJS_DBL_SIGNIFICAND_SIZE)
+#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_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     \
+#define NJS_SIGNIFICAND_SHIFT       (NJS_DIYFP_SIGNIFICAND_SIZE               \
                                      - NJS_DBL_SIGNIFICAND_SIZE)
 
 #define NJS_DECIMAL_EXPONENT_OFF    348
@@ -78,13 +87,9 @@ njs_d2diyfp(double d)
 njs_inline double
 njs_diyfp2d(njs_diyfp_t v)
 {
-    int           exp;
-    uint64_t      significand, biased_exp;
-
-    union {
-        double    d;
-        uint64_t  u64;
-    } u;
+    int               exp;
+    uint64_t          significand, biased_exp;
+    njs_diyfp_conv_t  conv;
 
     exp = v.exp;
     significand = v.significand;
@@ -118,10 +123,10 @@ njs_diyfp2d(njs_diyfp_t v)
         biased_exp = (uint64_t) (exp + NJS_DBL_EXPONENT_BIAS);
     }
 
-    u.u64 = (significand & NJS_DBL_SIGNIFICAND_MASK)
-            | (biased_exp << NJS_DBL_SIGNIFICAND_SIZE);
+    conv.u64 = (significand & NJS_DBL_SIGNIFICAND_MASK)
+                | (biased_exp << NJS_DBL_SIGNIFICAND_SIZE);
 
-    return u.d;
+    return conv.d;
 }
 
 
diff -r 0d119e8ce71a -r fdd56914c9ca src/njs_main.h
--- a/src/njs_main.h	Thu Oct 31 22:18:41 2019 +0300
+++ b/src/njs_main.h	Sun Nov 10 01:35:02 2019 +0300
@@ -15,6 +15,7 @@
 #include <njs_clang.h>
 #include <njs_str.h>
 #include <njs_utf8.h>
+#include <njs_diyfp.h>
 #include <njs_dtoa.h>
 #include <njs_dtoa_fixed.h>
 #include <njs_strtod.h>
diff -r 0d119e8ce71a -r fdd56914c9ca src/njs_math.c
--- a/src/njs_math.c	Thu Oct 31 22:18:41 2019 +0300
+++ b/src/njs_math.c	Sun Nov 10 01:35:02 2019 +0300
@@ -746,21 +746,62 @@ static njs_int_t
 njs_object_math_round(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    njs_int_t  ret;
+    uint8_t           sign;
+    uint64_t          one, fraction_mask;
+    njs_int_t         ret, biased_exp;
+    njs_diyfp_conv_t  conv;
 
     if (njs_slow_path(nargs < 2)) {
         njs_set_number(&vm->retval, NAN);
         return NJS_OK;
     }
 
-    if (njs_slow_path(!njs_is_number(&args[1]))) {
+    if (njs_slow_path(!njs_is_numeric(&args[1]))) {
         ret = njs_value_to_numeric(vm, &args[1], &args[1]);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
     }
 
-    njs_set_number(&vm->retval, round(njs_number(&args[1])));
+    conv.d = njs_number(&args[1]);
+    biased_exp = (conv.u64 & NJS_DBL_EXPONENT_MASK) >> NJS_DBL_SIGNIFICAND_SIZE;
+
+    if (biased_exp < NJS_DBL_EXPONENT_OFFSET) {
+
+        /* |v| < 1. */
+
+        if (biased_exp == (NJS_DBL_EXPONENT_OFFSET - 1)
+            && conv.u64 != njs_uint64(0xbfe00000, 0x00000000))
+        {
+            /* (|v| > 0.5 || v == 0.5) => +-1.0 */
+
+            conv.u64 = (conv.u64 & NJS_DBL_SIGN_MASK)
+                       | (NJS_DBL_EXPONENT_OFFSET << NJS_DBL_SIGNIFICAND_SIZE);
+
+        } else {
+
+            /* (|v| < 0.5 || v == -0.5) => +-0. */
+
+            conv.u64 &= ((uint64_t) 1) << 63;
+        }
+
+    } else if (biased_exp < NJS_DBL_EXPONENT_BIAS) {
+
+        /* |v| <= 2^52 - 1 (largest safe integer). */
+
+        one = ((uint64_t) 1) << (NJS_DBL_EXPONENT_BIAS - biased_exp);
+        fraction_mask = one - 1;
+
+        /* truncation. */
+
+        sign = conv.u64 >> 63;
+        conv.u64 += (one >> 1) - sign;
+        conv.u64 &= ~fraction_mask;
+    }
+
+    /* |v| >= 2^52, Infinity and NaNs => v. */
+
+    njs_set_number(&vm->retval, conv.d);
 
     return NJS_OK;
 }
diff -r 0d119e8ce71a -r fdd56914c9ca src/njs_number.c
--- a/src/njs_number.c	Thu Oct 31 22:18:41 2019 +0300
+++ b/src/njs_number.c	Sun Nov 10 01:35:02 2019 +0300
@@ -6,7 +6,6 @@
 
 
 #include <njs_main.h>
-#include <njs_diyfp.h>
 
 
 /*
diff -r 0d119e8ce71a -r fdd56914c9ca src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Oct 31 22:18:41 2019 +0300
+++ b/src/test/njs_unit_test.c	Sun Nov 10 01:35:02 2019 +0300
@@ -12995,8 +12995,26 @@ static njs_unit_test_t  njs_test[] =
       njs_str("-0") },
 
     { njs_str("Math.round(-0.5)"),
+      njs_str("-0") },
+
+    { njs_str("Math.round(-0.50000000000000001)"),
+      njs_str("-0") },
+
+    { njs_str("Math.round(-0.5000000000000001)"),
       njs_str("-1") },
 
+    { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round((2**32) + v) - 2**32)"),
+      njs_str("1,1,1") },
+
+    { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round(-(2**32) + v) + 2**32)"),
+      njs_str("1,1,1") },
+
+    { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round((2**32) - v) - 2**32)"),
+      njs_str("-1,0,0") },
+
+    { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round(-(2**32) - v) + 2**32)"),
+      njs_str("-1,0,0") },
+
     { njs_str("Math.sign(5)"),
       njs_str("1") },
 


More information about the nginx-devel mailing list