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

Dmitry Volyntsev xeioex at nginx.com
Tue Sep 3 14:36:59 UTC 2019


details:   https://hg.nginx.org/njs/rev/d46a332c9c4d
branches:  
changeset: 1156:d46a332c9c4d
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Sep 03 17:31:45 2019 +0300
description:
Added Number.prototype.toFixed().

This closes #29 issue on Github.

diffstat:

 auto/sources             |    1 +
 src/njs_dtoa_fixed.c     |  456 +++++++++++++++++++++++++++++++++++++++++++++++
 src/njs_dtoa_fixed.h     |   13 +
 src/njs_main.h           |    1 +
 src/njs_number.c         |  104 ++++++++++
 src/test/njs_unit_test.c |   79 ++++++++
 6 files changed, 654 insertions(+), 0 deletions(-)

diffs (709 lines):

diff -r 98d3dd617ed7 -r d46a332c9c4d auto/sources
--- a/auto/sources	Fri Aug 23 13:25:50 2019 -0400
+++ b/auto/sources	Tue Sep 03 17:31:45 2019 +0300
@@ -1,6 +1,7 @@
 NJS_LIB_SRCS=" \
    src/njs_diyfp.c \
    src/njs_dtoa.c \
+   src/njs_dtoa_fixed.c \
    src/njs_strtod.c \
    src/njs_murmur_hash.c \
    src/njs_djb_hash.c \
diff -r 98d3dd617ed7 -r d46a332c9c4d src/njs_dtoa_fixed.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/njs_dtoa_fixed.c	Tue Sep 03 17:31:45 2019 +0300
@@ -0,0 +1,456 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ *
+ * An internal fixed_dtoa() implementation based upon V8
+ * src/numbers/fixed-dtoa.cc without bignum support.
+ *
+ * Copyright 2011 the V8 project authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file.
+ */
+
+#include <njs_main.h>
+#include <njs_diyfp.h>
+
+
+typedef struct {
+    uint64_t  high;
+    uint64_t  low;
+} njs_diyu128_t;
+
+
+#define njs_diyu128(_h, _l)       (njs_diyu128_t) { .high = (_h), .low = (_l) }
+
+
+njs_inline njs_diyu128_t
+njs_diyu128_mul(njs_diyu128_t v, uint32_t multiplicand)
+{
+    uint32_t  part;
+    uint64_t  accumulator;
+
+    accumulator = (v.low & UINT32_MAX) * multiplicand;
+    part = (uint32_t) (accumulator & UINT32_MAX);
+
+    accumulator >>= 32;
+    accumulator = accumulator + (v.low >> 32) * multiplicand;
+    v.low = (accumulator << 32) + part;
+
+    accumulator >>= 32;
+    accumulator = accumulator + (v.high & UINT32_MAX) * multiplicand;
+    part = (uint32_t) (accumulator & UINT32_MAX);
+
+    accumulator >>= 32;
+    accumulator = accumulator + (v.high >> 32) * multiplicand;
+    v.high = (accumulator << 32) + part;
+
+    return v;
+}
+
+
+njs_inline njs_diyu128_t
+njs_diyu128_shift(njs_diyu128_t v, njs_int_t shift)
+{
+    /* -64 <= shift <= 64.*/
+
+    if (shift < 0) {
+        if (shift == -64) {
+            v.high = v.low;
+            v.low = 0;
+
+        } else {
+            v.high <<= -shift;
+            v.high += v.low >> (64 + shift);
+            v.low <<= -shift;
+        }
+
+        return v;
+    }
+
+    if (shift > 0) {
+        if (shift == 64) {
+            v.low = v.high;
+            v.high = 0;
+
+        } else {
+            v.low >>= shift;
+            v.low += v.high << (64 - shift);
+            v.high >>= shift;
+        }
+    }
+
+    return v;
+}
+
+
+njs_inline njs_int_t
+njs_diyu128_div_mod_pow2(njs_diyu128_t *v, njs_int_t power)
+{
+    uint64_t   part_low, part_high;
+    njs_int_t  result;
+
+    if (power >= 64) {
+        result = (int) (v->high >> (power - 64));
+        v->high -= (uint64_t) result << (power - 64);
+
+        return result;
+    }
+
+    part_low = v->low >> power;
+    part_high = v->high << (64 - power);
+
+    result = (int) (part_low + part_high);
+
+    v->low -= part_low << power;
+    v->high = 0;
+
+    return result;
+}
+
+
+njs_inline njs_bool_t
+njs_diyu128_is_zero(njs_diyu128_t v)
+{
+    if (v.low == 0 && v.high == 0) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+njs_inline njs_uint_t
+njs_diyu128_bit_at(njs_diyu128_t v, njs_uint_t pos)
+{
+    if (pos >= 64) {
+        return (njs_uint_t) (v.high >> (pos - 64)) & 1;
+    }
+
+    return (njs_uint_t) (v.low >> pos) & 1;
+}
+
+
+static size_t
+njs_fill_digits32(uint32_t number, char *start, size_t length)
+{
+    char        c;
+    size_t      i, j, n;
+    njs_int_t   digit;
+
+    n = 0;
+
+    while (number != 0) {
+        digit = number % 10;
+        number /= 10;
+        start[length + n] = '0' + digit;
+        n++;
+    }
+
+    i = length;
+    j = length + n - 1;
+
+    while (i < j) {
+        c = start[i];
+        start[i] = start[j];
+        start[j] = c;
+
+        i++;
+        j--;
+    }
+
+    return length + n;
+}
+
+
+njs_inline size_t
+njs_fill_digits32_fixed_length(uint32_t number, size_t requested_length,
+    char *start, size_t length)
+{
+    size_t  i;
+
+    i = requested_length;
+
+    while (i-- > 0) {
+        start[length + i] = '0' + number % 10;
+        number /= 10;
+    }
+
+    return length + requested_length;
+}
+
+
+njs_inline size_t
+njs_fill_digits64(uint64_t number, char *start, size_t length)
+{
+    uint32_t  part0, part1, part2, ten7;
+
+    ten7 = 10000000;
+
+    part2 = (uint32_t) (number % ten7);
+    number /= ten7;
+
+    part1 = (uint32_t) (number % ten7);
+    part0 = (uint32_t) (number / ten7);
+
+    if (part0 != 0) {
+        length = njs_fill_digits32(part0, start, length);
+        length = njs_fill_digits32_fixed_length(part1, 7, start, length);
+        return njs_fill_digits32_fixed_length(part2, 7, start, length);
+    }
+
+    if (part1 != 0) {
+        length = njs_fill_digits32(part1, start, length);
+        return njs_fill_digits32_fixed_length(part2, 7, start, length);
+    }
+
+    return njs_fill_digits32(part2, start, length);
+}
+
+
+njs_inline size_t
+njs_fill_digits64_fixed_length(uint64_t number, char *start, size_t length)
+{
+    uint32_t  part0, part1, part2, ten7;
+
+    ten7 = 10000000;
+
+    part2 = (uint32_t) (number % ten7);
+    number /= ten7;
+
+    part1 = (uint32_t) (number % ten7);
+    part0 = (uint32_t) (number / ten7);
+
+    length = njs_fill_digits32_fixed_length(part0, 3, start, length);
+    length = njs_fill_digits32_fixed_length(part1, 7, start, length);
+
+    return njs_fill_digits32_fixed_length(part2, 7, start, length);
+}
+
+
+njs_inline size_t
+njs_dtoa_round_up(char *start, size_t length, njs_int_t *point)
+{
+    size_t  i;
+
+    if (length == 0) {
+        start[0] = '1';
+        *point = 1;
+        return 1;
+    }
+
+    start[length - 1]++;
+
+    for (i = length - 1; i > 0; --i) {
+        if (start[i] != '0' + 10) {
+            return length;
+        }
+
+        start[i] = '0';
+        start[i - 1]++;
+    }
+
+    if (start[0] == '0' + 10) {
+        start[0] = '1';
+        (*point)++;
+    }
+
+    return length;
+}
+
+
+static size_t
+njs_fill_fractionals(uint64_t fractionals, int exponent, njs_uint_t frac,
+    char *start, size_t length, njs_int_t *point)
+{
+    njs_int_t      n, digit;
+    njs_uint_t     i;
+    njs_diyu128_t  fractionals128;
+
+    /*
+     * 128 <= exponent <= 0.
+     * 0 <= fractionals * 2^exponent < 1.
+     */
+
+    if (-exponent <= 64) {
+        /* fractionals <= 2^56. */
+
+        n = -exponent;
+
+        for (i = 0; i < frac && fractionals != 0; ++i) {
+            /*
+             * Multiplication by 10 is replaced with multiplication by 5 and
+             * point location adjustment. To avoid integer-overflow.
+             */
+
+            fractionals *= 5;
+            n--;
+
+            digit = (njs_int_t) (fractionals >> n);
+            fractionals -= (uint64_t) digit << n;
+
+            start[length++] = '0' + digit;
+        }
+
+        if (n > 0 && ((fractionals >> (n - 1)) & 1)) {
+            length = njs_dtoa_round_up(start, length, point);
+        }
+
+    } else {
+
+        fractionals128 = njs_diyu128(fractionals, 0);
+        fractionals128 = njs_diyu128_shift(fractionals128, -exponent - 64);
+
+        n = 128;
+
+        for (i = 0; i < frac && !njs_diyu128_is_zero(fractionals128); ++i) {
+            /*
+             * Multiplication by 10 is replaced with multiplication by 5 and
+             * point location adjustment. To avoid integer-overflow.
+             */
+
+            fractionals128 = njs_diyu128_mul(fractionals128, 5);
+            n--;
+
+            digit = njs_diyu128_div_mod_pow2(&fractionals128, n);
+
+            start[length++] = '0' + digit;
+        }
+
+        if (njs_diyu128_bit_at(fractionals128, n - 1)) {
+            length = njs_dtoa_round_up(start, length, point);
+        }
+    }
+
+    return length;
+}
+
+
+njs_inline size_t
+njs_trim_zeroes(char *start, size_t length, njs_int_t *point)
+{
+    size_t  i, n;
+
+    while (length > 0 && start[length - 1] == '0') {
+        length--;
+    }
+
+    n = 0;
+
+    while (n < length && start[n] == '0') {
+        n++;
+    }
+
+    if (n != 0) {
+        for (i = n; i < length; ++i) {
+            start[i - n] = start[i];
+        }
+
+        length -= n;
+        *point -= n;
+    }
+
+    return length;
+}
+
+
+size_t
+njs_fixed_dtoa(double value, njs_uint_t frac, char *start, njs_int_t *point)
+{
+    size_t       length;
+    uint32_t     quotient;
+    uint64_t     significand, divisor, dividend, remainder, integral, fract;
+    njs_int_t    exponent;
+    njs_diyfp_t  v;
+
+    length = 0;
+    v = njs_d2diyfp(value);
+
+    significand = v.significand;
+    exponent = v.exp;
+
+    /* exponent <= 19. */
+
+    if (exponent + NJS_SIGNIFICAND_SIZE > 64) {
+        /* exponent > 11. */
+
+        divisor = njs_uint64(0xB1, 0xA2BC2EC5); /* 5 ^ 17 */
+
+        dividend = significand;
+
+        /*
+         * Let v = f * 2^e with f == significand and e == exponent.
+         * Then need q (quotient) and r (remainder) as follows:
+         *   f * 2^e      = q * 5^17 * 2^17 + r
+         * If e > 17 then
+         *   f * 2^(e-17) = q * 5^17        + r/2^17
+         * else
+         *   f  = q * 5^17 * 2^(17-e) + r/2^e
+         */
+
+        if (exponent > 17) {
+            /* (e - 17) <= 3. */
+            dividend <<= exponent - 17;
+
+            quotient = (uint32_t) (dividend / divisor);
+            remainder = (dividend % divisor) << 17;
+
+        } else {
+            divisor <<= 17 - exponent;
+
+            quotient = (uint32_t) (dividend / divisor);
+            remainder = (dividend % divisor) << exponent;
+        }
+
+        length = njs_fill_digits32(quotient, start, length);
+        length = njs_fill_digits64_fixed_length(remainder, start, length);
+        *point = (njs_int_t) length;
+
+    } else if (exponent >= 0) {
+        /* 0 <= exponent <= 11. */
+
+        significand <<= exponent;
+        length = njs_fill_digits64(significand, start, length);
+        *point = (njs_int_t) length;
+
+    } else if (exponent > -NJS_SIGNIFICAND_SIZE) {
+        /* -53 < exponent < 0. */
+
+        integral = significand >> -exponent;
+        fract = significand - (integral << -exponent);
+
+        if (integral > UINT32_MAX) {
+            length = njs_fill_digits64(integral, start, length);
+
+        } else {
+            length = njs_fill_digits32((uint32_t) integral, start, length);
+        }
+
+        *point = (njs_int_t) length;
+        length = njs_fill_fractionals(fract, exponent, frac, start, length,
+                                      point);
+
+    } else if (exponent < -128) {
+        /* Valid for frac =< 20 only. TODO: bignum support. */
+
+        start[0] = '\0';
+
+        *point = -frac;
+
+    } else {
+        /* -128 <= exponent <= -53. */
+
+        *point = 0;
+        length = njs_fill_fractionals(significand, exponent, frac, start,
+                                      length, point);
+    }
+
+    length = njs_trim_zeroes(start, length, point);
+    start[length] = '\0';
+
+    if (length == 0) {
+        *point = -frac;
+    }
+
+    return length;
+}
diff -r 98d3dd617ed7 -r d46a332c9c4d src/njs_dtoa_fixed.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/njs_dtoa_fixed.h	Tue Sep 03 17:31:45 2019 +0300
@@ -0,0 +1,13 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#ifndef _NJS_DTOA_FIXED_H_INCLUDED_
+#define _NJS_DTOA_FIXED_H_INCLUDED_
+
+NJS_EXPORT size_t njs_fixed_dtoa(double value, njs_uint_t frac, char *start,
+    njs_int_t *point);
+
+#endif /* _NJS_DTOA_FIXED_H_INCLUDED_ */
diff -r 98d3dd617ed7 -r d46a332c9c4d src/njs_main.h
--- a/src/njs_main.h	Fri Aug 23 13:25:50 2019 -0400
+++ b/src/njs_main.h	Tue Sep 03 17:31:45 2019 +0300
@@ -16,6 +16,7 @@
 #include <njs_str.h>
 #include <njs_utf8.h>
 #include <njs_dtoa.h>
+#include <njs_dtoa_fixed.h>
 #include <njs_strtod.h>
 #include <njs_djb_hash.h>
 #include <njs_murmur_hash.h>
diff -r 98d3dd617ed7 -r d46a332c9c4d src/njs_number.c
--- a/src/njs_number.c	Fri Aug 23 13:25:50 2019 -0400
+++ b/src/njs_number.c	Tue Sep 03 17:31:45 2019 +0300
@@ -541,6 +541,101 @@ njs_number_prototype_to_string(njs_vm_t 
 }
 
 
+static njs_int_t
+njs_number_prototype_to_fixed(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
+{
+    u_char       *p;
+    int32_t      frac;
+    double       number;
+    size_t       length, size;
+    njs_int_t    point, prefix, postfix;
+    njs_value_t  *value;
+    u_char       buf[128], buf2[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;
+        }
+    }
+
+    frac = njs_primitive_value_to_integer(njs_arg(args, nargs, 1));
+    if (njs_slow_path(frac < 0 || frac > 100)) {
+        njs_range_error(vm, "digits argument must be between 0 and 100");
+        return NJS_ERROR;
+    }
+
+    number = njs_number(value);
+
+    if (njs_slow_path(isnan(number) || fabs(number) >= 1e21)) {
+        return njs_number_to_string(vm, &vm->retval, value);
+    }
+
+    point = 0;
+    length = njs_fixed_dtoa(number, frac, (char *) buf, &point);
+
+    prefix = 0;
+    postfix = 0;
+
+    if (point <= 0) {
+        prefix = -point + 1;
+        point = 1;
+    }
+
+    if (prefix + (njs_int_t) length < point + frac) {
+        postfix = point + frac - length - prefix;
+    }
+
+    size = prefix + length + postfix + !!(number < 0);
+
+    if (frac > 0) {
+        size += njs_length(".");
+    }
+
+    p = buf2;
+
+    while (--prefix >= 0) {
+        *p++ = '0';
+    }
+
+    if (length != 0) {
+        p = njs_cpymem(p, buf, length);
+    }
+
+    while (--postfix >= 0) {
+        *p++ = '0';
+    }
+
+    p = njs_string_alloc(vm, &vm->retval, size, size);
+    if (njs_slow_path(p == NULL)) {
+        return NJS_ERROR;
+    }
+
+    if (number < 0) {
+        *p++ = '-';
+    }
+
+    p = njs_cpymem(p, buf2, point);
+
+    if (frac > 0) {
+        *p++ = '.';
+
+        p = njs_cpymem(p, &buf2[point], frac);
+    }
+
+    return NJS_OK;
+}
+
+
 /*
  * The radix equal to 2 produces the longest intergral value of a number
  * and the maximum value consists of 1024 digits and minus sign.
@@ -641,6 +736,15 @@ static const njs_object_prop_t  njs_numb
         .writable = 1,
         .configurable = 1,
     },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("toFixed"),
+        .value = njs_native_function(njs_number_prototype_to_fixed,
+                                     NJS_SKIP_ARG, NJS_INTEGER_ARG),
+        .writable = 1,
+        .configurable = 1,
+    },
 };
 
 
diff -r 98d3dd617ed7 -r d46a332c9c4d src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Fri Aug 23 13:25:50 2019 -0400
+++ b/src/test/njs_unit_test.c	Tue Sep 03 17:31:45 2019 +0300
@@ -427,6 +427,85 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("NaN.toString(NaN)"),
       njs_str("RangeError") },
 
+    /* Number.prototype.toFixed(frac) method. */
+
+    { njs_str("(900.1).toFixed(1)"),
+      njs_str("900.1") },
+
+    { njs_str("(0).toFixed(0)"),
+      njs_str("0") },
+
+    { njs_str("(0).toFixed(3)"),
+      njs_str("0.000") },
+
+    { njs_str("(7).toFixed()"),
+      njs_str("7") },
+
+    { njs_str("(7).toFixed(0)"),
+      njs_str("7") },
+
+    { njs_str("(7).toFixed(2)"),
+      njs_str("7.00") },
+
+    { njs_str("(-900.1).toFixed(3.3)"),
+      njs_str("-900.100") },
+
+    { njs_str("(900.123).toFixed(5)"),
+      njs_str("900.12300") },
+
+    { njs_str("(1/3).toFixed(5)"),
+      njs_str("0.33333") },
+
+    { njs_str("(new Number(1/3)).toFixed(5)"),
+      njs_str("0.33333") },
+
+    { njs_str("(new Number(1/3)).toFixed(5)"),
+      njs_str("0.33333") },
+
+    { njs_str("(1/3).toFixed({toString(){return '5'}})"),
+      njs_str("0.33333") },
+
+    { njs_str("(1/3).toFixed(100)"),
+      njs_str("0.3333333333333333148296162562473909929394721984863281250000000000000000000000000000000000000000000000") },
+
+    { njs_str("(1.23e+20).toFixed(2)"),
+      njs_str("123000000000000000000.00") },
+
+    { njs_str("(1.23e-10).toFixed(2)"),
+      njs_str("0.00") },
+
+    { njs_str("(1.23e-10).toFixed(15)"),
+      njs_str("0.000000000123000") },
+
+    { njs_str("(1.23e-10).toFixed(100)"),
+      njs_str("0.0000000001229999999999999888422768137255427361997917046210204716771841049194335937500000000000000000") },
+
+    { njs_str("NaN.toFixed(1)"),
+      njs_str("NaN") },
+
+#if 0  /* FIXME: bignum support is requred to support frac >= 20 */
+    { njs_str("(2 ** -100).toFixed(100)"),
+      njs_str("0.0000000000000000000000000000007888609052210118054117285652827862296732064351090230047702789306640625") },
+#endif
+
+    { njs_str("(1000000000000000128).toString()"),
+      njs_str("1000000000000000100") },
+
+    { njs_str("(1000000000000000128).toFixed(0)"),
+      njs_str("1000000000000000128") },
+
+    { njs_str("(1e21).toFixed(1)"),
+      njs_str("1e+21") },
+
+    { njs_str("Number.prototype.toFixed.call({})"),
+      njs_str("TypeError: unexpected value type:object") },
+
+    { njs_str("(0).toFixed(-1)"),
+      njs_str("RangeError: digits argument must be between 0 and 100") },
+
+    { njs_str("(0).toFixed(101)"),
+      njs_str("RangeError: digits argument must be between 0 and 100") },
+
     /* An object "valueOf/toString" methods. */
 
     { njs_str("var a = { valueOf: function() { return 1 } };    +a"),


More information about the nginx-devel mailing list