From ad608e30dc5f909365f2b527d0513a48bed30ae1 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Fri, 14 Jul 2017 19:38:13 +0200 Subject: [PATCH] Improve toFixed function Fixes #1367. From now numbers are represented as binary floating-point which guarantees the expected operation of toFixed function. JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- .../ecma/base/ecma-helpers-conversion.c | 133 ++++++++++++++++++ jerry-core/ecma/base/ecma-helpers.h | 3 + .../ecma-builtin-number-prototype.c | 73 +++++++--- tests/jerry/number-prototype-to-fixed.js | 12 +- 4 files changed, 199 insertions(+), 22 deletions(-) diff --git a/jerry-core/ecma/base/ecma-helpers-conversion.c b/jerry-core/ecma/base/ecma-helpers-conversion.c index 234528dbd..d968b713f 100644 --- a/jerry-core/ecma/base/ecma-helpers-conversion.c +++ b/jerry-core/ecma/base/ecma-helpers-conversion.c @@ -322,6 +322,8 @@ ECMA_NUMBER_CONVERSION_128BIT_INTEGER_CHECK_PARTS_ARE_32BIT (name); \ } +#define EPSILON 0.0000001 + /** * @} */ @@ -930,6 +932,137 @@ 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 __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 __attr_always_inline___ +ecma_double_to_ascii (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; + int32_t num_of_digits = ecma_number_of_digits (val); + + 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 __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 i, char_cnt = 0; + double integer_part, fraction_part; + + fraction_part = fmod (val, 1.0); + integer_part = floor (val); + + lit_utf8_byte_t integer_part_buffer[ecma_number_of_digits (integer_part) + 1]; + + if (fabs (integer_part) < EPSILON) + { + buffer_p[0] = '0'; + char_cnt++; + } + else if (integer_part < 10e16) /* Ensure that integer_part is not rounded */ + { + while (integer_part > 0.0) + { + integer_part_buffer[char_cnt++] = (lit_utf8_byte_t) ((int) fmod (integer_part, 10.0) + '0'); + integer_part = floor (integer_part / 10.0); + } + + for (i = 0; i < char_cnt; i++) + { + buffer_p[i] = integer_part_buffer[char_cnt - i - 1]; + } + } + else + { + ecma_double_to_ascii (val, buffer_p, &char_cnt); + } + + *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. + */ +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 1731a5942..f8e98d0c9 100644 --- a/jerry-core/ecma/base/ecma-helpers.h +++ b/jerry-core/ecma/base/ecma-helpers.h @@ -250,6 +250,9 @@ ecma_number_t ecma_number_substract (ecma_number_t left_num, ecma_number_t right ecma_number_t ecma_number_multiply (ecma_number_t left_num, ecma_number_t right_num); ecma_number_t ecma_number_divide (ecma_number_t left_num, ecma_number_t right_num); 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-values-collection.c */ ecma_collection_header_t *ecma_new_values_collection (const ecma_value_t values_buffer[], ecma_length_t values_number, 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 e5ba6b018..8a36250ab 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c @@ -125,6 +125,41 @@ ecma_builtin_number_prototype_helper_to_string (lit_utf8_byte_t *digits_p, /**< return (lit_utf8_size_t) (p - to_digits_p); } /* ecma_builtin_number_prototype_helper_to_string */ +static inline lit_utf8_size_t __attr_always_inline___ +ecma_builtin_binary_floating_number_to_string (lit_utf8_byte_t *digits_p, /**< number as string + * in binary-floating point number */ + 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; + /* 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. */ + while (to_num_digits > 0) + { + *p++ = num_digits == 1 ? '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 * @@ -135,14 +170,15 @@ ecma_builtin_number_prototype_helper_round (lit_utf8_byte_t *digits_p, /**< [in, * form */ lit_utf8_size_t num_digits, /**< length of the string representation */ int32_t round_num, /**< number of digits to keep */ - int32_t *exponent_p) /**< [in, out] decimal exponent */ + int32_t *exponent_p, /**< [in, out] decimal exponent */ + bool zero) /**< true if digits_p represents zero */ { if (round_num < 1) { return 0; } - if ((lit_utf8_size_t) round_num >= num_digits) + if ((lit_utf8_size_t) round_num >= num_digits || zero) { return num_digits; } @@ -509,7 +545,7 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this bool is_negative = false; if (ecma_number_is_negative (this_num)) { - is_negative = true; + is_negative = ecma_number_is_zero (this_num) ? false : true; this_num *= -1; } @@ -537,15 +573,19 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this lit_utf8_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER]; lit_utf8_size_t num_digits; int32_t exponent; + int32_t frac_digits = ecma_number_to_int32 (arg_num); if (!ecma_number_is_zero (this_num)) { - num_digits = ecma_number_to_decimal (this_num, digits, &exponent); + num_digits = ecma_number_to_binary_floating_point_number (this_num, digits, &exponent); } else { - digits[0] = '0'; - num_digits = 1; + for (int32_t i = 0; i <= frac_digits; i++) + { + digits[i] = '0'; + } + num_digits = (lit_utf8_size_t) frac_digits + 1; exponent = 1; } @@ -558,12 +598,11 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this else { /* 1. */ - int32_t frac_digits = ecma_number_to_int32 (arg_num); - num_digits = ecma_builtin_number_prototype_helper_round (digits, - num_digits, + num_digits + 1, exponent + frac_digits, - &exponent); + &exponent, + ecma_number_is_zero (this_num) ? true : false); /* Buffer that is used to construct the string. */ int buffer_size = (exponent > 0) ? exponent + frac_digits + 2 : frac_digits + 3; @@ -585,11 +624,11 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this 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_number_prototype_helper_to_string (digits, - num_digits, - exponent, - p, - to_num_digits); + p += ecma_builtin_binary_floating_number_to_string (digits, + num_digits, + exponent, + p, + to_num_digits); JERRY_ASSERT (p - buff < buffer_size); /* String terminator. */ @@ -698,7 +737,7 @@ ecma_builtin_number_prototype_object_to_exponential (ecma_value_t this_arg, /**< frac_digits = ecma_number_to_int32 (arg_num); } - num_digits = ecma_builtin_number_prototype_helper_round (digits, num_digits, frac_digits + 1, &exponent); + 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; @@ -842,7 +881,7 @@ ecma_builtin_number_prototype_object_to_precision (ecma_value_t this_arg, /**< t int32_t precision = ecma_number_to_int32 (arg_num); - num_digits = ecma_builtin_number_prototype_helper_round (digits, num_digits, precision, &exponent); + num_digits = ecma_builtin_number_prototype_helper_round (digits, num_digits, precision, &exponent, false); int buffer_size; if (exponent < -5 || exponent > precision) diff --git a/tests/jerry/number-prototype-to-fixed.js b/tests/jerry/number-prototype-to-fixed.js index ba693f067..67d5ed1bf 100644 --- a/tests/jerry/number-prototype-to-fixed.js +++ b/tests/jerry/number-prototype-to-fixed.js @@ -31,14 +31,16 @@ assert((-Infinity).toFixed(0) === "-Infinity"); assert(NaN.toFixed(0) === "NaN"); 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) === "123456789012345680000.00000000000000000000"); +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) === "0.99"); +assert((9.995).toFixed(2) === "9.99"); +assert((7.995).toFixed(2) === "8.00"); +assert((8.995).toFixed(2) === "8.99"); assert((99.995).toFixed(2) === "100.00"); try {