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