[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