From 98b9be2dbe0d0712e274bf8e9d1d9a1d024bc45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20B=C3=A1tyai?= Date: Tue, 9 Jun 2015 13:03:57 +0200 Subject: [PATCH] Implement printing in the specified radix for Number.prototype.toString(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai.u-szeged@partner.samsung.com --- .../ecma-builtin-number-prototype.cpp | 197 +++++++++++++++++- tests/jerry/number-prototype-to-string.js | 102 +++++++++ 2 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 tests/jerry/number-prototype-to-string.js diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.cpp b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.cpp index fe75481ec5..a4c75a9fac 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.cpp +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.cpp @@ -24,7 +24,9 @@ #include "ecma-objects.h" #include "ecma-string-object.h" #include "ecma-try-catch-macro.h" +#include "fdlibm-math.h" #include "jrt.h" +#include "jrt-libc-includes.h" #ifndef CONFIG_ECMA_COMPACT_PROFILE_DISABLE_NUMBER_BUILTIN @@ -118,14 +120,205 @@ ecma_builtin_number_prototype_object_to_string (ecma_value_t this_arg, /**< this return ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE)); } - if (arguments_list_len == 0) + if (arguments_list_len == 0 + || ecma_number_is_nan (this_arg_number) + || ecma_number_is_infinity (this_arg_number) + || ecma_number_is_zero (this_arg_number)) { ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number); return ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p)); } + else { - ECMA_BUILTIN_CP_UNIMPLEMENTED (arguments_list_p); + const ecma_char_t digit_chars[36] = + { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + ECMA_OP_TO_NUMBER_TRY_CATCH (arg_num, arguments_list_p[0], ret_value); + + uint32_t radix = ecma_number_to_uint32 (arg_num); + + if (radix < 2 || radix > 36) + { + ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_RANGE)); + } + else if (radix == 10) + { + ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number); + + ret_value = ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p)); + } + else + { + /* Allocate a buffer that will be used to construct the number. */ + const int buff_size = 1080; + ecma_char_t buff[buff_size]; + int buff_index = 0; + + uint64_t digits; + int32_t num_digits; + int32_t exponent; + bool is_negative = false; + + if (ecma_number_is_negative (this_arg_number)) + { + this_arg_number = -this_arg_number; + is_negative = true; + } + + ecma_number_to_decimal (this_arg_number, &digits, &num_digits, &exponent); + + exponent = exponent - num_digits; + bool is_scale_negative = false; + + /* Calculate the scale of the number in the specified radix. */ + int scale = (int) -floor ((log (10) / log (radix)) * exponent); + + if (scale < 0) + { + is_scale_negative = true; + scale = -scale; + } + + /* Normalize the number, so that it is as close to 0 exponent as possible. */ + for (int i = 0; i < scale; i++) + { + if (is_scale_negative) + { + this_arg_number /= (ecma_number_t) radix; + } + else + { + this_arg_number *= (ecma_number_t) radix; + } + } + + uint64_t whole = (uint64_t) this_arg_number; + double fraction = this_arg_number - (double) whole; + + /* Calculate digits for whole part. */ + while (whole > 0) + { + buff[buff_index++] = (ecma_char_t) (whole % radix); + whole /= radix; + } + + /* Calculate where we have to put the radix point. */ + int point = is_scale_negative ? buff_index + scale : buff_index - scale; + + /* Reverse the digits, since they are backwards. */ + for (int i = 0; i < buff_index / 2; i++) + { + ecma_char_t swap = buff[i]; + buff[i] = buff[buff_index - i - 1]; + buff[buff_index - i - 1] = swap; + } + + bool should_round = false; + /* Calculate digits for fractional part. */ + for (int iter_count = 0; (fraction > 0 || iter_count < scale) && buff_index < buff_size - 2; iter_count++) + { + fraction *= radix; + ecma_char_t digit = (ecma_char_t) floor (fraction); + + buff[buff_index++] = digit; + fraction -= floor (fraction); + + if (iter_count == scale && is_scale_negative) + { + /* + * When scale is negative, that means the original number did not have a fractional part, + * but by normalizing it, we introduced one. In this case, when the iteration count reaches + * the scale, we already have the number, but it may be incorrect, so we calculate + * one extra digit that we round off just to make sure. + */ + should_round = true; + break; + } + } + + if (should_round) + { + /* Round off last digit. */ + if (buff[buff_index - 1] > radix / 2) + { + buff[buff_index - 2]++; + } + + buff_index--; + + /* Propagate carry. */ + for (int i = buff_index - 1; i > 0 && buff[i] >= radix; i--) + { + buff[i] = (ecma_char_t) (buff[i] - radix); + buff[i - 1]++; + } + + /* Carry propagated over the whole number, need to add a leading digit. */ + if (buff[0] >= radix) + { + memmove (buff + 1, buff, (size_t) buff_index); + buff_index++; + buff[0] = 1; + } + } + + /* Remove trailing zeros from fraction. */ + while (buff_index - 1 > point && buff[buff_index - 1] == 0) + { + buff_index--; + } + + /* Add leading zeros in case place of radix point is negative. */ + if (point <= 0) + { + memmove (buff - point + 1, buff, (size_t) buff_index); + buff_index += -point + 1; + + for (int i = 0; i < -point + 1; i++) + { + buff[i] = 0; + } + + point = 1; + } + + /* Convert digits to characters. */ + for (int i = 0; i < buff_index; i++) + { + buff[i] = digit_chars[buff[i]]; + } + + /* Place radix point to the required position. */ + if (point < buff_index) + { + memmove (buff + point + 1, buff + point, (size_t) buff_index); + buff[point] = '.'; + buff_index++; + } + + /* Add negative sign if necessary. */ + if (is_negative) + { + memmove (buff + 1, buff, (size_t) buff_index); + buff_index++; + buff[0] = '-'; + } + + /* Add zero terminator. */ + buff[buff_index] = '\0'; + + ecma_string_t* str_p = ecma_new_ecma_string ((ecma_char_t *) buff); + ret_value = ecma_make_normal_completion_value (ecma_make_string_value (str_p)); + } + ECMA_OP_TO_NUMBER_FINALIZE (arg_num); + return ret_value; } } /* ecma_builtin_number_prototype_object_to_string */ diff --git a/tests/jerry/number-prototype-to-string.js b/tests/jerry/number-prototype-to-string.js new file mode 100644 index 0000000000..16b9682a5e --- /dev/null +++ b/tests/jerry/number-prototype-to-string.js @@ -0,0 +1,102 @@ +// Copyright 2015 Samsung Electronics Co., Ltd. +// Copyright 2015 University of Szeged. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +assert((NaN).toString() === "NaN"); +assert((-Infinity).toString() === "-Infinity"); +assert((Infinity).toString() === "Infinity"); +assert((NaN).toString(6) === "NaN"); +assert((-Infinity).toString(7) === "-Infinity"); +assert((Infinity).toString(8) === "Infinity"); +assert((16).toString(16) === "10"); +assert((15).toString(16) === "f"); +assert((12.5).toString(4) === "30.2"); +assert((0.005).toString(4) === "0.000110132232011013223201101323"); +assert((2000).toString(4) === "133100"); +assert((2000).toString(3) === "2202002"); +assert((2000).toString(16) === "7d0"); +assert((0.03125).toString(2) === "0.00001"); +assert((0.03125).toString(16) === "0.08"); +assert((0.0001).toString(4) === "0.000000122031232023223013010030231") +assert((0).toString(16) === "0"); +assert((-16).toString(16) === "-10"); +assert((-15).toString(16) === "-f"); +assert((-12.5).toString(4) === "-30.2"); +assert((-0.005).toString(4) === "-0.000110132232011013223201101323"); +assert((-2000).toString(4) === "-133100"); +assert((-2000).toString(3) === "-2202002"); +assert((-2000).toString(16) === "-7d0"); +assert((-0.03125).toString(2) === "-0.00001"); +assert((-0.03125).toString(16) === "-0.08"); +assert((-0.0001).toString(4) === "-0.000000122031232023223013010030231") +assert((-0).toString(16) === "0"); + +assert((123400).toString(2) === "11110001000001000"); +assert((123400).toString(3) === "20021021101"); +assert((123400).toString(4) === "132020020"); +assert((123400).toString(5) === "12422100"); +assert((123400).toString(6) === "2351144"); +assert((123400).toString(7) === "1022524"); +assert((123400).toString(8) === "361010"); +assert((123400).toString(9) === "207241"); +assert((123400).toString(10) === "123400"); +assert((123400).toString(11) === "84792"); +assert((123400).toString(12) === "5b4b4"); +assert((123400).toString(13) === "44224"); +assert((123400).toString(14) === "32d84"); +assert((123400).toString(15) === "2686a"); +assert((123400).toString(16) === "1e208"); +assert((123400).toString(17) === "181ge"); +assert((123400).toString(18) === "132fa"); +assert((123400).toString(19) === "hife"); +assert((123400).toString(20) === "f8a0"); +assert((123400).toString(21) === "d6h4"); +assert((123400).toString(22) === "bcl2"); +assert((123400).toString(23) === "a365"); +assert((123400).toString(24) === "8m5g"); +assert((123400).toString(25) === "7mb0"); +assert((123400).toString(26) === "70e4"); +assert((123400).toString(27) === "677a"); +assert((123400).toString(28) === "5hb4"); +assert((123400).toString(29) === "51l5"); +assert((123400).toString(30) === "4h3a"); +assert((123400).toString(31) === "44ck"); +assert((123400).toString(32) === "3og8"); +assert((123400).toString(33) === "3ead"); +assert((123400).toString(34) === "34pe"); +assert((123400).toString(35) === "2upp"); +assert((123400).toString(36) === "2n7s"); + +assert((123400).toString(new Number(16)) === "1e208"); + +try { + assert((123).toString(1)); + assert(false) +} catch (e) { + assert(e instanceof RangeError); +} + +try { + assert((123).toString(37)); + assert(false) +} catch (e) { + assert(e instanceof RangeError); +} + +try { + assert((123).toString(Infinity)); + assert(false) +} catch (e) { + assert(e instanceof RangeError); +}