[njs] Added Number.prototype.toPrecision().

Dmitry Volyntsev xeioex at nginx.com
Thu Oct 3 13:59:39 UTC 2019


details:   https://hg.nginx.org/njs/rev/956b24477d59
branches:  
changeset: 1169:956b24477d59
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Thu Oct 03 16:59:22 2019 +0300
description:
Added Number.prototype.toPrecision().

This closes #221 issue on Github.

diffstat:

 src/njs_dtoa.c           |  235 +++++++++++++++++++++++++++++++++++++++++++++++
 src/njs_dtoa.h           |    1 +
 src/njs_number.c         |   56 +++++++++++
 src/test/njs_unit_test.c |   85 +++++++++++++++++
 4 files changed, 377 insertions(+), 0 deletions(-)

diffs (448 lines):

diff -r cb7874a63d2b -r 956b24477d59 src/njs_dtoa.c
--- a/src/njs_dtoa.c	Thu Oct 03 16:26:04 2019 +0300
+++ b/src/njs_dtoa.c	Thu Oct 03 16:59:22 2019 +0300
@@ -53,6 +53,40 @@ njs_round(char *start, size_t length, ui
 }
 
 
+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;
+
+    if (unit >= ten_kappa || ten_kappa - unit <= unit) {
+        return;
+    }
+
+    if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit)) {
+        return;
+    }
+
+    if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit))) {
+        start[length - 1]++;
+
+        for (i = length - 1; i > 0; --i) {
+            if (start[i] != '0' + 10) {
+                break;
+            }
+
+            start[i] = '0';
+            start[i - 1]++;
+        }
+
+        if (start[0] == '0' + 10) {
+            start[0] = '1';
+            *kappa += 1;
+        }
+    }
+}
+
+
 njs_inline int
 njs_dec_count(uint32_t n)
 {
@@ -170,6 +204,79 @@ njs_digit_gen(njs_diyfp_t v, njs_diyfp_t
 }
 
 
+njs_inline size_t
+njs_digit_gen_prec(njs_diyfp_t v, size_t prec, char *start, int *dec_exp)
+{
+    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;
+        }
+    }
+
+    /* 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;
+}
+
+
 njs_inline njs_diyfp_t
 njs_diyfp_normalize_boundary(njs_diyfp_t v)
 {
@@ -235,6 +342,27 @@ njs_grisu2(double value, char *start, in
 
 
 njs_inline size_t
+njs_grisu2_prec(double value, char *start, size_t prec, int *point)
+{
+    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;
+}
+
+
+njs_inline size_t
 njs_write_exponent(int exp, char *start)
 {
     char      *p;
@@ -338,6 +466,78 @@ njs_dtoa_format(char *start, size_t len,
 }
 
 
+njs_inline size_t
+njs_dtoa_prec_format(char *start, size_t prec, size_t len, int point)
+{
+    int     exponent;
+    char    *p;
+    size_t  m, rest, size;
+
+    exponent = point - 1;
+
+    if (exponent < -6 || exponent >= (int) prec) {
+        p = &start[len];
+        if (prec != 1) {
+            memmove(&start[2], &start[1], len - 1);
+            start[1] = '.';
+            p++;
+        }
+
+        njs_memset(p, '0', prec - len);
+        p += prec - len;
+
+        *p++ = 'e';
+
+        size = njs_write_exponent(exponent, p);
+
+        return prec + 1 + (prec != 1) + size;
+    }
+
+    /* 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);
+        }
+
+        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] = '.';
+
+            njs_memset(&start[point + 1], '0', prec - len);
+        }
+
+    } 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);
+}
+
+
 size_t
 njs_dtoa(double value, char *start)
 {
@@ -366,3 +566,38 @@ njs_dtoa(double value, char *start)
 
     return njs_dtoa_format(p, length, dec_exp) + minus;
 }
+
+
+/*
+ * 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)
+{
+    int     point, minus;
+    char    *p;
+    size_t  length;
+
+    /* Not handling NaN and inf. */
+
+    p = start;
+    minus = 0;
+
+    if (value != 0) {
+        if (value < 0) {
+            *p++ = '-';
+            value = -value;
+            minus = 1;
+        }
+
+        length = njs_grisu2_prec(value, p, prec, &point);
+
+    } else {
+        start[0] = '0';
+        length = 1;
+        point = 1;
+    }
+
+    return njs_dtoa_prec_format(p, prec, length, point) + minus;
+}
diff -r cb7874a63d2b -r 956b24477d59 src/njs_dtoa.h
--- a/src/njs_dtoa.h	Thu Oct 03 16:26:04 2019 +0300
+++ b/src/njs_dtoa.h	Thu Oct 03 16:59:22 2019 +0300
@@ -8,5 +8,6 @@
 #define _NJS_DTOA_H_INCLUDED_
 
 NJS_EXPORT size_t njs_dtoa(double value, char *start);
+NJS_EXPORT size_t njs_dtoa_precision(double value, char *start, size_t prec);
 
 #endif /* _NJS_DTOA_H_INCLUDED_ */
diff -r cb7874a63d2b -r 956b24477d59 src/njs_number.c
--- a/src/njs_number.c	Thu Oct 03 16:26:04 2019 +0300
+++ b/src/njs_number.c	Thu Oct 03 16:59:22 2019 +0300
@@ -637,6 +637,53 @@ njs_number_prototype_to_fixed(njs_vm_t *
 }
 
 
+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)
+{
+    double       number;
+    size_t       size;
+    int32_t      precision;
+    njs_value_t  *value;
+    u_char       buf[128];
+
+    /* 128 > 100 + 21 + njs_length(".-\0"). */
+
+    value = &args[0];
+
+    if (value->type != NJS_NUMBER) {
+        if (value->type == NJS_OBJECT_NUMBER) {
+            value = njs_object_value(value);
+
+        } else {
+            njs_type_error(vm, "unexpected value type:%s",
+                           njs_type_string(value->type));
+            return NJS_ERROR;
+        }
+    }
+
+    if (njs_is_undefined(njs_arg(args, nargs, 1))) {
+        return njs_number_to_string(vm, &vm->retval, value);
+    }
+
+    number = njs_number(value);
+
+    if (njs_slow_path(isnan(number) || isinf(number))) {
+        return njs_number_to_string(vm, &vm->retval, value);
+    }
+
+    precision = njs_primitive_value_to_integer(njs_argument(args, 1));
+    if (njs_slow_path(precision < 1 || precision > 100)) {
+        njs_range_error(vm, "precision argument must be between 1 and 100");
+        return NJS_ERROR;
+    }
+
+    size = njs_dtoa_precision(number, (char *) buf, precision);
+
+    return njs_string_new(vm, &vm->retval, buf, size, size);
+}
+
+
 /*
  * The radix equal to 2 produces the longest  value for a number.
  */
@@ -802,6 +849,15 @@ static const njs_object_prop_t  njs_numb
         .writable = 1,
         .configurable = 1,
     },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("toPrecision"),
+        .value = njs_native_function(njs_number_prototype_to_precision,
+                                     NJS_SKIP_ARG, NJS_INTEGER_ARG),
+        .writable = 1,
+        .configurable = 1,
+    },
 };
 
 
diff -r cb7874a63d2b -r 956b24477d59 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Oct 03 16:26:04 2019 +0300
+++ b/src/test/njs_unit_test.c	Thu Oct 03 16:59:22 2019 +0300
@@ -525,6 +525,91 @@ static njs_unit_test_t  njs_test[] =
       njs_str("0.0000000000000000000000000000007888609052210118054117285652827862296732064351090230047702789306640625") },
 #endif
 
+    /* Number.prototype.toPrecision(prec) method. */
+
+    { njs_str("Array(4).fill().map((n, i) => i+1).map((v)=>(1/7).toPrecision(v))"),
+      njs_str("0.1,0.14,0.143,0.1429") },
+
+    { njs_str("Array(4).fill().map((n, i) => i+1).map((v)=>(0).toPrecision(v))"),
+      njs_str("0,0.0,0.00,0.000") },
+
+    { njs_str("Array(4).fill().map((n, i) => i+1).map((v)=>(1/2).toPrecision(v))"),
+      njs_str("0.5,0.50,0.500,0.5000") },
+
+    { njs_str("Array(6).fill().map((n, i) => i+2).map((v)=>(1/v).toPrecision(5))"),
+      njs_str("0.50000,0.33333,0.25000,0.20000,0.16667,0.14286") },
+
+    { njs_str("Array(6).fill().map((n, i) => i+2).map((v)=>(1/(v*100)).toPrecision(5))"),
+      njs_str("0.0050000,0.0033333,0.0025000,0.0020000,0.0016667,0.0014286") },
+
+    { njs_str("Array(6).fill().map((n, i) => i+1).map((v)=>(10*v/7).toPrecision(5))"),
+      njs_str("1.4286,2.8571,4.2857,5.7143,7.1429,8.5714") },
+
+    { njs_str("Array(6).fill().map((n, i) => i+1).map((v)=>(v/3).toPrecision(5))"),
+      njs_str("0.33333,0.66667,1.0000,1.3333,1.6667,2.0000") },
+
+    { njs_str("Array(6).fill().map((n, i) => i+1).map((v)=>((Math.pow(-1,v))*(2*v)/3).toPrecision(5))"),
+      njs_str("-0.66667,1.3333,-2.0000,2.6667,-3.3333,4.0000") },
+
+    { njs_str("Array(12).fill().map((n, i) => i-3).map((v)=>(2**v).toPrecision(6))"),
+      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("Array(3).fill().map((n, i) => i + 19).map((v)=>(2**(-v)).toPrecision(20))"),
+      njs_str("0.0000019073486328125000000,9.5367431640625000000e-7,4.7683715820312500000e-7") },
+
+    { 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") },
+
+    { njs_str("(2**128).toPrecision(2)"),
+      njs_str("3.4e+38") },
+
+    { njs_str("(2**128).toPrecision(40)"),
+      njs_str("340282366920938463490000000000000000000.0") },
+
+    { njs_str("(123).toPrecision(0)"),
+      njs_str("RangeError: precision argument must be between 1 and 100") },
+
+    { njs_str("(123).toPrecision(2.4)"),
+      njs_str("1.2e+2") },
+
+    { njs_str("(123).toPrecision(101)"),
+      njs_str("RangeError: precision argument must be between 1 and 100") },
+
+    { njs_str("(2**10000).toPrecision()"),
+      njs_str("Infinity") },
+
+    { njs_str("(-(2**10000)).toPrecision()"),
+      njs_str("-Infinity") },
+
+    { njs_str("(-0).toPrecision(2)"),
+      njs_str("0.0") },
+
+    { njs_str("NaN.toPrecision()"),
+      njs_str("NaN") },
+
+    { njs_str("NaN.toPrecision(0)"),
+      njs_str("NaN") },
+
+    { njs_str("(10**22).toPrecision()"),
+      njs_str("1e+22") },
+
+    { njs_str("Number.prototype.toPrecision.call('12')"),
+      njs_str("TypeError: unexpected value type:string") },
+
     { njs_str("(1000000000000000128).toString()"),
       njs_str("1000000000000000100") },
 


More information about the nginx-devel mailing list