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
This commit is contained in:
@@ -322,6 +322,8 @@
|
|||||||
ECMA_NUMBER_CONVERSION_128BIT_INTEGER_CHECK_PARTS_ARE_32BIT (name); \
|
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);
|
return ecma_errol0_dtoa ((double) num, out_digits_p, out_decimal_exp_p);
|
||||||
} /* ecma_number_to_decimal */
|
} /* 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
|
* Convert ecma-number to zero-terminated string
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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_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);
|
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_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-helpers-values-collection.c */
|
||||||
ecma_collection_header_t *ecma_new_values_collection (const ecma_value_t values_buffer[], ecma_length_t values_number,
|
ecma_collection_header_t *ecma_new_values_collection (const ecma_value_t values_buffer[], ecma_length_t values_number,
|
||||||
|
|||||||
@@ -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);
|
return (lit_utf8_size_t) (p - to_digits_p);
|
||||||
} /* ecma_builtin_number_prototype_helper_to_string */
|
} /* 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
|
* Helper for rounding numbers
|
||||||
*
|
*
|
||||||
@@ -135,14 +170,15 @@ ecma_builtin_number_prototype_helper_round (lit_utf8_byte_t *digits_p, /**< [in,
|
|||||||
* form */
|
* form */
|
||||||
lit_utf8_size_t num_digits, /**< length of the string representation */
|
lit_utf8_size_t num_digits, /**< length of the string representation */
|
||||||
int32_t round_num, /**< number of digits to keep */
|
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)
|
if (round_num < 1)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((lit_utf8_size_t) round_num >= num_digits)
|
if ((lit_utf8_size_t) round_num >= num_digits || zero)
|
||||||
{
|
{
|
||||||
return num_digits;
|
return num_digits;
|
||||||
}
|
}
|
||||||
@@ -509,7 +545,7 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this
|
|||||||
bool is_negative = false;
|
bool is_negative = false;
|
||||||
if (ecma_number_is_negative (this_num))
|
if (ecma_number_is_negative (this_num))
|
||||||
{
|
{
|
||||||
is_negative = true;
|
is_negative = ecma_number_is_zero (this_num) ? false : true;
|
||||||
this_num *= -1;
|
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_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER];
|
||||||
lit_utf8_size_t num_digits;
|
lit_utf8_size_t num_digits;
|
||||||
int32_t exponent;
|
int32_t exponent;
|
||||||
|
int32_t frac_digits = ecma_number_to_int32 (arg_num);
|
||||||
|
|
||||||
if (!ecma_number_is_zero (this_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
|
else
|
||||||
{
|
{
|
||||||
digits[0] = '0';
|
for (int32_t i = 0; i <= frac_digits; i++)
|
||||||
num_digits = 1;
|
{
|
||||||
|
digits[i] = '0';
|
||||||
|
}
|
||||||
|
num_digits = (lit_utf8_size_t) frac_digits + 1;
|
||||||
exponent = 1;
|
exponent = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,12 +598,11 @@ ecma_builtin_number_prototype_object_to_fixed (ecma_value_t this_arg, /**< this
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* 1. */
|
/* 1. */
|
||||||
int32_t frac_digits = ecma_number_to_int32 (arg_num);
|
|
||||||
|
|
||||||
num_digits = ecma_builtin_number_prototype_helper_round (digits,
|
num_digits = ecma_builtin_number_prototype_helper_round (digits,
|
||||||
num_digits,
|
num_digits + 1,
|
||||||
exponent + frac_digits,
|
exponent + frac_digits,
|
||||||
&exponent);
|
&exponent,
|
||||||
|
ecma_number_is_zero (this_num) ? true : false);
|
||||||
|
|
||||||
/* Buffer that is used to construct the string. */
|
/* Buffer that is used to construct the string. */
|
||||||
int buffer_size = (exponent > 0) ? exponent + frac_digits + 2 : frac_digits + 3;
|
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 to_num_digits = ((exponent > 0) ? (lit_utf8_size_t) (exponent + frac_digits)
|
||||||
: (lit_utf8_size_t) (frac_digits + 1));
|
: (lit_utf8_size_t) (frac_digits + 1));
|
||||||
p += ecma_builtin_number_prototype_helper_to_string (digits,
|
p += ecma_builtin_binary_floating_number_to_string (digits,
|
||||||
num_digits,
|
num_digits,
|
||||||
exponent,
|
exponent,
|
||||||
p,
|
p,
|
||||||
to_num_digits);
|
to_num_digits);
|
||||||
|
|
||||||
JERRY_ASSERT (p - buff < buffer_size);
|
JERRY_ASSERT (p - buff < buffer_size);
|
||||||
/* String terminator. */
|
/* 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);
|
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. */
|
/* frac_digits + 2 characters for number, 5 characters for exponent, 1 for \0. */
|
||||||
int buffer_size = frac_digits + 2 + 5 + 1;
|
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);
|
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;
|
int buffer_size;
|
||||||
if (exponent < -5 || exponent > precision)
|
if (exponent < -5 || exponent > precision)
|
||||||
|
|||||||
@@ -31,14 +31,16 @@ assert((-Infinity).toFixed(0) === "-Infinity");
|
|||||||
assert(NaN.toFixed(0) === "NaN");
|
assert(NaN.toFixed(0) === "NaN");
|
||||||
assert((0.0).toFixed(0) === "0");
|
assert((0.0).toFixed(0) === "0");
|
||||||
assert((0.0).toFixed(1) === "0.0");
|
assert((0.0).toFixed(1) === "0.0");
|
||||||
assert((-0.0).toFixed(0) === "-0");
|
assert((-0.0).toFixed(0) === "0");
|
||||||
assert((-0.0).toFixed(1) === "-0.0");
|
assert((-0.0).toFixed(1) === "0.0");
|
||||||
assert((123456789012345678901.0).toFixed(20) === "123456789012345680000.00000000000000000000");
|
assert((123456789012345678901.0).toFixed(20) === "123456789012345683968.00000000000000000000");
|
||||||
assert((123.56).toFixed(NaN) === "124");
|
assert((123.56).toFixed(NaN) === "124");
|
||||||
assert((123.56).toFixed(-0.9) === "124");
|
assert((123.56).toFixed(-0.9) === "124");
|
||||||
assert((0.095).toFixed(2) === "0.10");
|
assert((0.095).toFixed(2) === "0.10");
|
||||||
//assert((0.995).toFixed(2) === "0.99");
|
assert((0.995).toFixed(2) === "0.99");
|
||||||
//assert((9.995).toFixed(2) === "9.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");
|
assert((99.995).toFixed(2) === "100.00");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user