details: https://hg.nginx.org/njs/rev/956b24477d59 branches: changeset: 1169:956b24477d59 user: Dmitry Volyntsev <xei...@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") }, _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel