From 2fac7cc85e56150ac45500b36874d26f1db727db Mon Sep 17 00:00:00 2001 From: Szilagyi Adam Date: Tue, 21 Jul 2020 12:46:03 +0200 Subject: [PATCH] Refactor Number.prototype methods toFixed, toExponential, toPrecision (#3911) JerryScript-DCO-1.0-Signed-off-by: Adam Szilagyi aszilagy@inf.u-szeged.hu --- .../ecma/base/ecma-helpers-conversion.c | 136 ---- jerry-core/ecma/base/ecma-helpers.h | 3 - .../ecma-builtin-number-prototype.c | 721 ++++++------------ .../jerry/number-prototype-to-exponential.js | 8 +- tests/jerry/number-prototype-to-fixed.js | 17 +- tests/jerry/number-prototype-to-precision.js | 12 +- 6 files changed, 255 insertions(+), 642 deletions(-) diff --git a/jerry-core/ecma/base/ecma-helpers-conversion.c b/jerry-core/ecma/base/ecma-helpers-conversion.c index 80232f7a5..89b89bd7a 100644 --- a/jerry-core/ecma/base/ecma-helpers-conversion.c +++ b/jerry-core/ecma/base/ecma-helpers-conversion.c @@ -877,142 +877,6 @@ ecma_number_to_decimal (ecma_number_t num, /**< ecma-number */ return ecma_errol0_dtoa ((double) num, out_digits_p, out_decimal_exp_p); } /* ecma_number_to_decimal */ -/** - * Calculate the number of digits from the given double value whithout franction part - * - * @return number of digits - */ -inline static int32_t JERRY_ATTR_ALWAYS_INLINE -ecma_number_of_digits (double val) /**< ecma number */ -{ - JERRY_ASSERT (fabs (fmod (val, 1.0)) < EPSILON); - int32_t exponent = 0; - - while (val >= 1.0) - { - val /= 10.0; - exponent++; - } - - return exponent; -} /* ecma_number_of_digits */ - -/** - * Convert double value to ASCII - */ -inline static void JERRY_ATTR_ALWAYS_INLINE -ecma_double_to_ascii (double val, /**< ecma number */ - lit_utf8_byte_t *buffer_p, /**< buffer to generate digits into */ - int32_t num_of_digits, /**< number of digits */ - int32_t *exp_p) /**< [out] exponent */ -{ - int32_t char_cnt = 0; - - double divider = 10.0; - double prev_residual; - double mod_res = fmod (val, divider); - - buffer_p[num_of_digits - 1 - char_cnt++] = (lit_utf8_byte_t) ((int) mod_res + '0'); - divider *= 10.0; - prev_residual = mod_res; - - while (char_cnt < num_of_digits) - { - mod_res = fmod (val, divider); - double residual = mod_res - prev_residual; - buffer_p[num_of_digits - 1 - char_cnt++] = (lit_utf8_byte_t) ((int) (residual / (divider / 10.0)) + '0'); - - divider *= 10.0; - prev_residual = mod_res; - } - - *exp_p = char_cnt; -} /* ecma_double_to_ascii */ - -/** - * Double to binary floating-point number conversion - * - * @return number of generated digits - */ -static inline lit_utf8_size_t JERRY_ATTR_ALWAYS_INLINE -ecma_double_to_binary_floating_point (double val, /**< ecma number */ - lit_utf8_byte_t *buffer_p, /**< buffer to generate digits into */ - int32_t *exp_p) /**< [out] exponent */ -{ - int32_t char_cnt = 0; - double integer_part, fraction_part; - - fraction_part = fmod (val, 1.0); - integer_part = floor (val); - int32_t num_of_digits = ecma_number_of_digits (integer_part); - - if (fabs (integer_part) < EPSILON) - { - buffer_p[0] = '0'; - char_cnt++; - } - else if (num_of_digits <= 16) /* Ensure that integer_part is not rounded */ - { - while (integer_part > 0.0) - { - buffer_p[num_of_digits - 1 - char_cnt++] = (lit_utf8_byte_t) ((int) fmod (integer_part, 10.0) + '0'); - integer_part = floor (integer_part / 10.0); - } - } - else if (num_of_digits <= 21) - { - ecma_double_to_ascii (integer_part, buffer_p, num_of_digits, &char_cnt); - } - else - { - /* According to ECMA-262 v5, 15.7.4.5, step 7: if x >= 10^21, then execution will continue with - * ToString(x) so in this case no further conversions are required. Number 21 in the else if condition - * above must be kept in sync with the number 21 in ecma_builtin_number_prototype_object_to_fixed - * method, step 7. */ - *exp_p = num_of_digits; - return 0; - } - - *exp_p = char_cnt; - - while (fraction_part > 0 && char_cnt < ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER - 1) - { - fraction_part *= 10; - double tmp = fraction_part; - fraction_part = fmod (fraction_part, 1.0); - integer_part = floor (tmp); - buffer_p[char_cnt++] = (lit_utf8_byte_t) ('0' + (int) integer_part); - } - - buffer_p[char_cnt] = '\0'; - - return (lit_utf8_size_t) (char_cnt - *exp_p); -} /* ecma_double_to_binary_floating_point */ - -/** - * Perform conversion of ecma-number to equivalent binary floating-point number representation with decimal exponent. - * - * Note: - * The calculated values correspond to s, n, k parameters in ECMA-262 v5, 9.8.1, item 5: - * - parameter out_digits_p corresponds to s, the digits of the number; - * - parameter out_decimal_exp_p corresponds to n, the decimal exponent; - * - return value corresponds to k, the number of digits. - * - * @return the number of digits - */ -lit_utf8_size_t -ecma_number_to_binary_floating_point_number (ecma_number_t num, /**< ecma-number */ - lit_utf8_byte_t *out_digits_p, /**< [out] buffer to fill with digits */ - int32_t *out_decimal_exp_p) /**< [out] decimal exponent */ -{ - JERRY_ASSERT (!ecma_number_is_nan (num)); - JERRY_ASSERT (!ecma_number_is_zero (num)); - JERRY_ASSERT (!ecma_number_is_infinity (num)); - JERRY_ASSERT (!ecma_number_is_negative (num)); - - return ecma_double_to_binary_floating_point ((double) num, out_digits_p, out_decimal_exp_p); -} /* ecma_number_to_binary_floating_point_number */ - /** * Convert ecma-number to zero-terminated string * diff --git a/jerry-core/ecma/base/ecma-helpers.h b/jerry-core/ecma/base/ecma-helpers.h index 5358f7541..6e5f384bc 100644 --- a/jerry-core/ecma/base/ecma-helpers.h +++ b/jerry-core/ecma/base/ecma-helpers.h @@ -421,9 +421,6 @@ ecma_value_t ecma_number_parse_float (const lit_utf8_byte_t *string_buff, lit_utf8_size_t string_buff_size); ecma_value_t ecma_integer_multiply (ecma_integer_value_t left_integer, ecma_integer_value_t right_integer); lit_utf8_size_t ecma_number_to_decimal (ecma_number_t num, lit_utf8_byte_t *out_digits_p, int32_t *out_decimal_exp_p); -lit_utf8_size_t ecma_number_to_binary_floating_point_number (ecma_number_t num, - lit_utf8_byte_t *out_digits_p, - int32_t *out_decimal_exp_p); /* ecma-helpers-collection.c */ ecma_collection_t *ecma_new_collection (void); diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c index 2402a3a9d..1157b78a2 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c @@ -27,6 +27,7 @@ #include "ecma-try-catch-macro.h" #include "jrt.h" #include "jrt-libc-includes.h" +#include "lit-char-helpers.h" #if ENABLED (JERRY_BUILTIN_NUMBER) @@ -66,123 +67,6 @@ enum * @{ */ -/** - * Helper for stringifying numbers - * - * @return the length of the generated string representation - */ -static lit_utf8_size_t -ecma_builtin_number_prototype_helper_to_string (lit_utf8_byte_t *digits_p, /**< number as string in decimal form */ - lit_utf8_size_t num_digits, /**< length of the string representation */ - int32_t exponent, /**< decimal exponent */ - lit_utf8_byte_t *to_digits_p, /**< [out] buffer to write */ - lit_utf8_size_t to_num_digits) /**< requested number of digits */ -{ - lit_utf8_byte_t *p = to_digits_p; - - if (exponent <= 0) - { - /* Add zero to the integer part. */ - *p++ = '0'; - to_num_digits--; - - if (to_num_digits > 0) - { - *p++ = '.'; - - /* Add leading zeros to the fraction part. */ - for (int i = 0; i < -exponent && to_num_digits > 0; i++) - { - *p++ = '0'; - to_num_digits--; - } - } - } - else - { - /* Add significant digits of the integer part. */ - lit_utf8_size_t to_copy = JERRY_MIN (num_digits, to_num_digits); - to_copy = JERRY_MIN (to_copy, (lit_utf8_size_t) exponent); - memmove (p, digits_p, (size_t) to_copy); - p += to_copy; - to_num_digits -= to_copy; - digits_p += to_copy; - num_digits -= to_copy; - exponent -= (int32_t) to_copy; - - /* Add zeros before decimal point. */ - while (exponent > 0 && to_num_digits > 0) - { - JERRY_ASSERT (num_digits == 0); - *p++ = '0'; - to_num_digits--; - exponent--; - } - - if (to_num_digits > 0) - { - *p++ = '.'; - } - } - - if (to_num_digits > 0) - { - /* Add significant digits of the fraction part. */ - lit_utf8_size_t to_copy = JERRY_MIN (num_digits, to_num_digits); - memmove (p, digits_p, (size_t) to_copy); - p += to_copy; - to_num_digits -= to_copy; - - /* Add trailing zeros. */ - while (to_num_digits > 0) - { - *p++ = '0'; - to_num_digits--; - } - } - - return (lit_utf8_size_t) (p - to_digits_p); -} /* ecma_builtin_number_prototype_helper_to_string */ - -/** - * Helper function to convert a binary floating point number to string. - * - * @return size of result string - */ -static inline lit_utf8_size_t JERRY_ATTR_ALWAYS_INLINE -ecma_builtin_binary_floating_number_to_string (lit_utf8_byte_t *digits_p, /**< number as string - * in binary-floating point number */ - int32_t exponent, /**< decimal exponent */ - lit_utf8_byte_t *to_digits_p, /**< [out] buffer to write */ - lit_utf8_size_t to_num_digits) /**< requested number of digits */ -{ - lit_utf8_byte_t *p = to_digits_p; - /* Add significant digits of the decimal part. */ - while (exponent > 0) - { - *p++ = *digits_p++; - exponent--; - to_num_digits--; - } - - if (to_num_digits > 0) - { - *p++ = '.'; - } - - if (to_num_digits > 0) - { - /* Add significant digits of the fraction part and fill the remaining digits with zero */ - while (to_num_digits > 0) - { - *p++ = (*digits_p == 0 ? '0' : *digits_p++); - to_num_digits--; - } - } - - return (lit_utf8_size_t) (p - to_digits_p); -} /* ecma_builtin_binary_floating_number_to_string */ - /** * Helper for rounding numbers * @@ -196,6 +80,20 @@ ecma_builtin_number_prototype_helper_round (lit_utf8_byte_t *digits_p, /**< [in, int32_t *exponent_p, /**< [in, out] decimal exponent */ bool zero) /**< true if digits_p represents zero */ { + if (round_num == 0 && *exponent_p == 0) + { + if (digits_p[0] >= 5) + { + digits_p[0] = '1'; + } + else + { + digits_p[0] = '0'; + } + + return 1; + } + if (round_num < 1) { return 0; @@ -567,403 +465,273 @@ ecma_builtin_number_prototype_object_value_of (ecma_value_t this_arg) /**< this */ typedef enum { - NUMBER_ROUTINE_TO_FIXED, /**< Number.prototype.toFixed: ECMA-262 v5, 15.7.4.4 */ - NUMBER_ROUTINE_TO_EXPONENTIAL, /**< Number.prototype.toExponential: ECMA-262 v5, 15.7.4.5 */ - NUMBER_ROUTINE_TO_PRECISION, /**< Number.prototype.toPrecision: ECMA-262 v5, 15.7.4.6 */ + NUMBER_ROUTINE_TO_FIXED, /**< Number.prototype.toFixed: ECMA-262 v11, 20.1.3.3 */ + NUMBER_ROUTINE_TO_EXPONENTIAL, /**< Number.prototype.toExponential: ECMA-262 v11, 20.1.3.2 */ + NUMBER_ROUTINE_TO_PRECISION, /**< Number.prototype.toPrecision: ECMA-262 v11, 20.1.3.5 */ NUMBER_ROUTINE__COUNT, /**< count of the modes */ } number_routine_mode_t; /** - * Helper function for the Number.prototype object's - * 'toFixed', 'toExponential' and 'toPrecision' routines to - * check the special cases before the conversion - * - * @return ECMA_VALUE_EMPTY - if the conversion should continue - * ecma-value - otherwise + * Helper method to convert a number based on the given routine. */ static ecma_value_t -ecma_builtin_number_prepare_conversion (ecma_number_t *this_num_p, /**< [out] this argument number */ - ecma_value_t arg_1, /**< routine's argument */ - bool *is_negative_p, /**< [out] is negative */ - int32_t *arg_1_int32_p, /**< [out] routine's argument number - * converted to int32_t */ - number_routine_mode_t mode) /**< number routine mode */ +ecma_builtin_number_prototype_object_to_number_convert (ecma_number_t this_num, /**< this argument number */ + ecma_value_t arg, /**< routine's argument */ + number_routine_mode_t mode) /**< number routine mode */ { - JERRY_ASSERT (mode < NUMBER_ROUTINE__COUNT); + if (ecma_is_value_undefined (arg) + && mode == NUMBER_ROUTINE_TO_PRECISION) + { + return ecma_builtin_number_prototype_object_to_string (this_num, NULL, 0); + } ecma_number_t arg_num; - arg_1 = ecma_op_to_integer (arg_1, &arg_num); + ecma_value_t to_integer = ecma_op_to_integer (arg, &arg_num); - if (ECMA_IS_VALUE_ERROR (arg_1)) + if (ECMA_IS_VALUE_ERROR (to_integer)) { - return arg_1; + return to_integer; } + /* Argument boundary checks */ if (mode != NUMBER_ROUTINE_TO_PRECISION - && (arg_num <= -1 || arg_num >= 21)) + && (arg_num <= -1 || arg_num >= 101)) { - return ecma_raise_range_error (ECMA_ERR_MSG ("Fraction digits must be between 0 and 20.")); + return ecma_raise_range_error (ECMA_ERR_MSG ("Fraction digits must be between 0 and 100.")); + } + else if (mode == NUMBER_ROUTINE_TO_PRECISION + && (arg_num < 1 || arg_num > 100)) + { + return ecma_raise_range_error (ECMA_ERR_MSG ("Precision digits must be between 1 and 100.")); } - if (ecma_number_is_nan (*this_num_p)) + /* Handle NaN separately */ + if (ecma_number_is_nan (this_num)) { return ecma_make_magic_string_value (LIT_MAGIC_STRING_NAN); } - bool is_negative = false; - - if (ecma_number_is_negative (*this_num_p)) - { - is_negative = ecma_number_is_zero (*this_num_p) ? false : true; - *this_num_p *= -1; - } - - *is_negative_p = is_negative; - - /* We handle infinities separately. */ - if (ecma_number_is_infinity (*this_num_p)) - { - return ecma_make_magic_string_value ((is_negative ? LIT_MAGIC_STRING_NEGATIVE_INFINITY_UL - : LIT_MAGIC_STRING_INFINITY_UL)); - } - - if (mode == NUMBER_ROUTINE_TO_PRECISION && - (ecma_number_is_nan (arg_num) || arg_num < 1 || arg_num >= 22)) - { - return ecma_raise_range_error (ECMA_ERR_MSG ("Precision digits must be between 1 and 21.")); - } - - *arg_1_int32_p = ecma_number_to_int32 (arg_num); - - return ECMA_VALUE_EMPTY; -} /* ecma_builtin_number_prepare_conversion */ - -/** - * The Number.prototype object's 'toFixed' routine - * - * See also: - * ECMA-262 v5, 15.7.4.5 - * - * @return ecma value - * Returned value must be freed with ecma_free_value. - */ -static ecma_value_t -ecma_builtin_number_prototype_object_to_fixed (ecma_number_t this_num, /**< this argument number */ - ecma_value_t radix) /**< routine's argument */ -{ - bool is_negative; - int32_t frac_digits; - - ecma_value_t comp_value = ecma_builtin_number_prepare_conversion (&this_num, - radix, - &is_negative, - &frac_digits, - NUMBER_ROUTINE_TO_FIXED); - - if (!ecma_is_value_empty (comp_value)) - { - return comp_value; - } - - /* Get the parameters of the number if non-zero. */ + /* Get the parameters of the number */ lit_utf8_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER]; - lit_utf8_size_t num_digits; + lit_utf8_size_t num_of_digits; int32_t exponent; + int32_t arg_int = ecma_number_to_int32 (arg_num); + bool is_zero = ecma_number_is_zero (this_num); + bool is_negative = ecma_number_is_negative (this_num); - if (!ecma_number_is_zero (this_num)) + ecma_stringbuilder_t builder = ecma_stringbuilder_create (); + + if (is_negative) { - num_digits = ecma_number_to_binary_floating_point_number (this_num, digits, &exponent); - JERRY_ASSERT (exponent >= 0); - } - else - { - for (int32_t i = 0; i <= frac_digits; i++) + if (!is_zero) { - digits[i] = '0'; + ecma_stringbuilder_append_char (&builder, LIT_CHAR_MINUS); } - num_digits = (lit_utf8_size_t) frac_digits + 1; - exponent = 1; + + this_num *= -1; } - /* 7. */ - if (exponent > 21) + /* Handle zero separately */ + if (is_zero) { - return ecma_builtin_number_prototype_object_to_string (this_num, NULL, 0); - } - - /* 8. */ - num_digits = ecma_builtin_number_prototype_helper_round (digits, - num_digits + (lit_utf8_size_t) exponent, - exponent + frac_digits, - &exponent, - ecma_number_is_zero (this_num)); - - /* Buffer that is used to construct the string. */ - int buffer_size = (exponent > 0) ? exponent + frac_digits + 2 : frac_digits + 3; - - if (is_negative) - { - buffer_size++; - } - - JERRY_ASSERT (buffer_size > 0); - - ecma_value_t ret_value; - JMEM_DEFINE_LOCAL_ARRAY (buff, buffer_size, lit_utf8_byte_t); - - lit_utf8_byte_t *p = buff; - - if (is_negative) - { - *p++ = '-'; - } - - lit_utf8_size_t to_num_digits = ((exponent > 0) ? (lit_utf8_size_t) (exponent + frac_digits) - : (lit_utf8_size_t) (frac_digits + 1)); - p += ecma_builtin_binary_floating_number_to_string (digits, - exponent, - p, - to_num_digits); - - JERRY_ASSERT (p - buff < buffer_size); - /* String terminator. */ - *p = 0; - ecma_string_t *str = ecma_new_ecma_string_from_utf8 (buff, (lit_utf8_size_t) (p - buff)); - - ret_value = ecma_make_string_value (str); - JMEM_FINALIZE_LOCAL_ARRAY (buff); - - return ret_value; -} /* ecma_builtin_number_prototype_object_to_fixed */ - -/** - * The Number.prototype object's 'toExponential' routine - * - * See also: - * ECMA-262 v5, 15.7.4.6 - * - * @return ecma value - * Returned value must be freed with ecma_free_value. - */ -static ecma_value_t -ecma_builtin_number_prototype_object_to_exponential (ecma_number_t this_num, /**< this argument number */ - ecma_value_t fraction_digits) /**< routine's argument */ -{ - bool is_negative; - int32_t frac_digits; - - ecma_value_t comp_value = ecma_builtin_number_prepare_conversion (&this_num, - fraction_digits, - &is_negative, - &frac_digits, - NUMBER_ROUTINE_TO_EXPONENTIAL); - - if (!ecma_is_value_empty (comp_value)) - { - return comp_value; - } - - /* Get the parameters of the number if non zero. */ - lit_utf8_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER]; - lit_utf8_size_t num_digits; - int32_t exponent; - - if (!ecma_number_is_zero (this_num)) - { - num_digits = ecma_number_to_decimal (this_num, digits, &exponent); - } - else - { - digits[0] = '0'; - num_digits = 1; - exponent = 1; - } - - if (ecma_is_value_undefined (fraction_digits)) - { - frac_digits = (int32_t) num_digits - 1; - } - - num_digits = ecma_builtin_number_prototype_helper_round (digits, num_digits, frac_digits + 1, &exponent, false); - - /* frac_digits + 2 characters for number, 5 characters for exponent, 1 for \0. */ - int buffer_size = frac_digits + 2 + 5 + 1; - - if (is_negative) - { - /* +1 character for sign. */ - buffer_size++; - } - - ecma_value_t ret_value; - JMEM_DEFINE_LOCAL_ARRAY (buff, buffer_size, lit_utf8_byte_t); - - lit_utf8_byte_t *actual_char_p = buff; - - if (is_negative) - { - *actual_char_p++ = '-'; - } - - actual_char_p += ecma_builtin_number_prototype_helper_to_string (digits, - num_digits, - 1, - actual_char_p, - (lit_utf8_size_t) (frac_digits + 1)); - - *actual_char_p++ = 'e'; - - exponent--; - if (exponent < 0) - { - exponent *= -1; - *actual_char_p++ = '-'; - } - else - { - *actual_char_p++ = '+'; - } - - /* Add exponent digits. */ - actual_char_p += ecma_uint32_to_utf8_string ((uint32_t) exponent, actual_char_p, 3); - - JERRY_ASSERT (actual_char_p - buff < buffer_size); - *actual_char_p = '\0'; - ecma_string_t *str = ecma_new_ecma_string_from_utf8 (buff, (lit_utf8_size_t) (actual_char_p - buff)); - ret_value = ecma_make_string_value (str); - JMEM_FINALIZE_LOCAL_ARRAY (buff); - - return ret_value; -} /* ecma_builtin_number_prototype_object_to_exponential */ - -/** - * The Number.prototype object's 'toPrecision' routine - * - * See also: - * ECMA-262 v5, 15.7.4.7 - * - * @return ecma value - * Returned value must be freed with ecma_free_value. - */ -static ecma_value_t -ecma_builtin_number_prototype_object_to_precision (ecma_number_t this_num, /**< this argument number */ - ecma_value_t precision_value) /**< routine's argument */ -{ - if (ecma_is_value_undefined (precision_value)) - { - return ecma_builtin_number_prototype_object_to_string (this_num, NULL, 0); - } - - bool is_negative; - int32_t precision; - - ecma_value_t comp_value = ecma_builtin_number_prepare_conversion (&this_num, - precision_value, - &is_negative, - &precision, - NUMBER_ROUTINE_TO_PRECISION); - - if (!ecma_is_value_empty (comp_value)) - { - return comp_value; - } - - /* Get the parameters of the number if non-zero. */ - lit_utf8_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER]; - lit_utf8_size_t num_digits; - int32_t exponent; - - if (!ecma_number_is_zero (this_num)) - { - num_digits = ecma_number_to_decimal (this_num, digits, &exponent); - } - else - { - digits[0] = '0'; - num_digits = 1; - exponent = 1; - } - - num_digits = ecma_builtin_number_prototype_helper_round (digits, num_digits, precision, &exponent, false); - - int buffer_size; - if (exponent < -5 || exponent > precision) - { - /* Exponential notation, precision + 1 digits for number, 5 for exponent, 1 for \0 */ - buffer_size = precision + 1 + 5 + 1; - } - else if (exponent <= 0) - { - /* Fixed notation, -exponent + 2 digits for leading zeros, precision digits, 1 for \0 */ - buffer_size = -exponent + 2 + precision + 1; - } - else - { - /* Fixed notation, precision + 1 digits for number, 1 for \0 */ - buffer_size = precision + 1 + 1; - } - - if (is_negative) - { - buffer_size++; - } - - ecma_value_t ret_value; - JMEM_DEFINE_LOCAL_ARRAY (buff, buffer_size, lit_utf8_byte_t); - lit_utf8_byte_t *actual_char_p = buff; - - if (is_negative) - { - *actual_char_p++ = '-'; - } - - /* 10.c, Exponential notation.*/ - if (exponent < -5 || exponent > precision) - { - actual_char_p += ecma_builtin_number_prototype_helper_to_string (digits, - num_digits, - 1, - actual_char_p, - (lit_utf8_size_t) precision); - - *actual_char_p++ = 'e'; - - exponent--; - if (exponent < 0) + if (mode == NUMBER_ROUTINE_TO_PRECISION) { - exponent *= -1; - *actual_char_p++ = '-'; + arg_int--; + } + + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + + if (arg_int > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_DOT); + } + + for (int32_t i = 0; i < arg_int; i++) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + } + + if (mode == NUMBER_ROUTINE_TO_EXPONENTIAL) + { + ecma_stringbuilder_append_raw (&builder, (const lit_utf8_byte_t *) "e+0", 3); + } + + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); + } + + /* Handle infinity separately */ + if (ecma_number_is_infinity (this_num)) + { + ecma_stringbuilder_append_magic (&builder, LIT_MAGIC_STRING_INFINITY_UL); + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); + } + + num_of_digits = ecma_number_to_decimal (this_num, digits, &exponent); + + /* Handle undefined argument */ + if (ecma_is_value_undefined (arg) && mode == NUMBER_ROUTINE_TO_EXPONENTIAL) + { + arg_int = (int32_t) num_of_digits - 1; + } + + if (mode == NUMBER_ROUTINE_TO_FIXED + && exponent > 21) + { + ecma_stringbuilder_destroy (&builder); + + if (is_negative) + { + this_num *= -1; + } + + return ecma_builtin_number_prototype_object_to_string (this_num, NULL, 0); + } + + int32_t digits_to_keep = arg_int; + + if (mode == NUMBER_ROUTINE_TO_FIXED) + { + digits_to_keep += exponent; + } + else if (mode == NUMBER_ROUTINE_TO_EXPONENTIAL) + { + digits_to_keep += 1; + } + + num_of_digits = ecma_builtin_number_prototype_helper_round (digits, + num_of_digits, + digits_to_keep, + &exponent, + false); + + /* toExponent routine and toPrecision cases where the exponent > precision or exponent < -5 */ + if (mode == NUMBER_ROUTINE_TO_EXPONENTIAL + || (mode == NUMBER_ROUTINE_TO_PRECISION + && (exponent < -5 || exponent > arg_int))) + { + /* Append first digit */ + ecma_stringbuilder_append_byte (&builder, *digits); + + if (mode == NUMBER_ROUTINE_TO_PRECISION) + { + arg_int--; + } + + if (arg_int > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_DOT); + } + + /* Append significant fraction digits */ + ecma_stringbuilder_append_raw (&builder, digits + 1, num_of_digits - 1); + + /* Append leading zeros */ + for (int32_t i = (int32_t) (num_of_digits); i < arg_int + 1; i++) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + } + + ecma_stringbuilder_append_char (&builder, LIT_CHAR_LOWERCASE_E); + + if (exponent <= 0) + { + exponent = (-exponent) + 1; + ecma_stringbuilder_append_char (&builder, LIT_CHAR_MINUS); } else { - *actual_char_p++ = '+'; + exponent -= 1; + ecma_stringbuilder_append_char (&builder, LIT_CHAR_PLUS); } - /* Add exponent digits. */ - actual_char_p += ecma_uint32_to_utf8_string ((uint32_t) exponent, actual_char_p, 3); + /* Append exponent part */ + lit_utf8_size_t exp_size = ecma_uint32_to_utf8_string ((uint32_t) exponent, digits, 3); + ecma_stringbuilder_append_raw (&builder, digits, exp_size); + + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); + } + + /* toFixed routine and toPrecision cases where the exponent <= precision and exponent >= -5 */ + lit_utf8_size_t result_digits; + + if (mode == NUMBER_ROUTINE_TO_FIXED) + { + result_digits = ((exponent > 0) ? (lit_utf8_size_t) (exponent + arg_int) + : (lit_utf8_size_t) (arg_int + 1)); } - /* Fixed notation. */ else { - lit_utf8_size_t to_num_digits = ((exponent <= 0) ? (lit_utf8_size_t) (1 - exponent + precision) - : (lit_utf8_size_t) precision); - actual_char_p += ecma_builtin_number_prototype_helper_to_string (digits, - num_digits, - exponent, - actual_char_p, - to_num_digits); - + result_digits = ((exponent <= 0) ? (lit_utf8_size_t) (1 - exponent + arg_int) + : (lit_utf8_size_t) arg_int); } - JERRY_ASSERT (actual_char_p - buff < buffer_size); - *actual_char_p = '\0'; - ecma_string_t *str_p = ecma_new_ecma_string_from_utf8 (buff, (lit_utf8_size_t) (actual_char_p - buff)); + /* Number of digits we copied from digits array */ + lit_utf8_size_t copied_digits = 0; - ret_value = ecma_make_string_value (str_p); - JMEM_FINALIZE_LOCAL_ARRAY (buff); + if (exponent == 0 && digits_to_keep == 0) + { + ecma_stringbuilder_append_char (&builder, *digits); + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); + } - return ret_value; -} /* ecma_builtin_number_prototype_object_to_precision */ + if (exponent <= 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + result_digits--; + + if (result_digits > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_DOT); + + /* Append leading zeros to the fraction part */ + for (int32_t i = 0; i < -exponent && result_digits > 0; i++) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + result_digits--; + } + } + } + else + { + /* Append significant digits of integer part */ + copied_digits = JERRY_MIN (JERRY_MIN (num_of_digits, result_digits), (lit_utf8_size_t) exponent); + ecma_stringbuilder_append_raw (&builder, digits, copied_digits); + + result_digits -= copied_digits; + num_of_digits -= copied_digits; + exponent -= (int32_t) copied_digits; + + /* Append zeros before decimal point */ + while (exponent > 0 && result_digits > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + result_digits--; + exponent--; + } + + if (result_digits > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_DOT); + } + } + + if (result_digits > 0) + { + /* Append significant digits to the fraction part */ + lit_utf8_size_t to_copy = JERRY_MIN (num_of_digits, result_digits); + ecma_stringbuilder_append_raw (&builder, digits + copied_digits, to_copy); + result_digits -= to_copy; + + /* Append leading zeros */ + while (result_digits > 0) + { + ecma_stringbuilder_append_char (&builder, LIT_CHAR_0); + result_digits--; + } + } + + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); +} /* ecma_builtin_number_prototype_object_to_number_convert */ /** * Dispatcher of the built-in's routines @@ -1006,16 +774,13 @@ ecma_builtin_number_prototype_dispatch_routine (uint16_t builtin_routine_id, /** return ecma_builtin_number_prototype_object_to_string (this_arg_number, NULL, 0); } case ECMA_NUMBER_PROTOTYPE_TO_FIXED: - { - return ecma_builtin_number_prototype_object_to_fixed (this_arg_number, routine_arg_1); - } case ECMA_NUMBER_PROTOTYPE_TO_EXPONENTIAL: - { - return ecma_builtin_number_prototype_object_to_exponential (this_arg_number, routine_arg_1); - } case ECMA_NUMBER_PROTOTYPE_TO_PRECISION: { - return ecma_builtin_number_prototype_object_to_precision (this_arg_number, routine_arg_1); + const int option = NUMBER_ROUTINE_TO_FIXED + (builtin_routine_id - ECMA_NUMBER_PROTOTYPE_TO_FIXED); + return ecma_builtin_number_prototype_object_to_number_convert (this_arg_number, + routine_arg_1, + (number_routine_mode_t) option); } default: { diff --git a/tests/jerry/number-prototype-to-exponential.js b/tests/jerry/number-prototype-to-exponential.js index b0416d85b..47271eee7 100644 --- a/tests/jerry/number-prototype-to-exponential.js +++ b/tests/jerry/number-prototype-to-exponential.js @@ -34,6 +34,7 @@ assert((123456789012345678901.0).toExponential(20) === "1.23456789012345680000e+ assert((123456789012345678901.0).toExponential("6") === "1.234568e+20"); assert((123.45).toExponential(3.2) === "1.235e+2"); assert((123.45).toExponential(-0.1) === "1e+2"); +assert((12).toExponential(21) === "1.200000000000000000000e+1") try { (12).toExponential(Number.MAX_VALUE); @@ -56,13 +57,6 @@ try { assert(e instanceof RangeError) } -try { - (12).toExponential(21); - assert(false); -} catch (e) { - assert(e instanceof RangeError) -} - try { Number.prototype.toExponential.call(new Object()); assert(false); diff --git a/tests/jerry/number-prototype-to-fixed.js b/tests/jerry/number-prototype-to-fixed.js index 637296cd4..38dc551c9 100644 --- a/tests/jerry/number-prototype-to-fixed.js +++ b/tests/jerry/number-prototype-to-fixed.js @@ -33,15 +33,17 @@ assert((0.0).toFixed(0) === "0"); assert((0.0).toFixed(1) === "0.0"); assert((-0.0).toFixed(0) === "0"); assert((-0.0).toFixed(1) === "0.0"); -assert((123456789012345678901.0).toFixed(20) === "123456789012345683968.00000000000000000000"); assert((123.56).toFixed(NaN) === "124"); assert((123.56).toFixed(-0.9) === "124"); assert((0.095).toFixed(2) === "0.10"); -assert((0.995).toFixed(2) === "0.99"); -assert((9.995).toFixed(2) === "9.99"); +assert((0.995).toFixed(2) === "1.00") +assert((9.995).toFixed(2) === "10.00"); assert((7.995).toFixed(2) === "8.00"); -assert((8.995).toFixed(2) === "8.99"); +assert((8.995).toFixed(2) === "9.00"); assert((99.995).toFixed(2) === "100.00"); +assert((12).toFixed(21) === "12.000000000000000000000"); +assert((-1111111111111111111111.12).toFixed(3) === "-1.1111111111111111e+21"); +assert((1111111111111111111111.12).toFixed(3) === "1.1111111111111111e+21"); try { Number.prototype.toExponential.call(new Object()); @@ -57,13 +59,6 @@ try { assert(e instanceof RangeError) } -try { - (12).toFixed(21); - assert(false); -} catch (e) { - assert(e instanceof RangeError) -} - assert ((0.5).toFixed(0) === "1"); assert ((1.5).toFixed(0) === "2"); assert ((12.5).toFixed(0) === "13"); diff --git a/tests/jerry/number-prototype-to-precision.js b/tests/jerry/number-prototype-to-precision.js index 10b774092..ba3bff868 100644 --- a/tests/jerry/number-prototype-to-precision.js +++ b/tests/jerry/number-prototype-to-precision.js @@ -32,9 +32,14 @@ assert((0.0).toPrecision(6) === "0.00000"); assert((123456789012345678901.0).toPrecision(20) === "1.2345678901234568000e+20"); assert((123456789012345678901.0).toPrecision(21) === "123456789012345680000"); assert((123456789012345678901.0).toPrecision("6") === "1.23457e+20"); +assert((0.0000004).toPrecision(2) === "4.0e-7"); +assert((0.000004).toPrecision(2) === "0.0000040"); +assert((1234.92).toPrecision(4) === "1235"); +assert((1234.92).toPrecision(3) === "1.23e+3"); assert((123.56).toPrecision(1.3) === "1e+2"); assert((123.56).toPrecision(21.9) === "123.560000000000000000"); +assert((12).toPrecision(22) === "12.00000000000000000000") assert(Number(982).toPrecision(1) === "1e+3"); assert(Number(982).toPrecision(2) === "9.8e+2"); @@ -48,13 +53,6 @@ try { assert(e instanceof RangeError) } -try { - (12).toPrecision(22); - assert(false); -} catch (e) { - assert(e instanceof RangeError) -} - try { Number.prototype.toExponential.call(new Object()); assert(false);