diff options
Diffstat (limited to 'jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c')
-rw-r--r-- | jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c | 295 |
1 files changed, 91 insertions, 204 deletions
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 2e94f3a1..1d212f5f 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.c @@ -21,6 +21,7 @@ #include "ecma-exceptions.h" #include "ecma-gc.h" #include "ecma-globals.h" +#include "ecma-helpers-number.h" #include "ecma-helpers.h" #include "ecma-objects.h" #include "ecma-string-object.h" @@ -133,6 +134,11 @@ ecma_builtin_number_prototype_helper_round (lit_utf8_byte_t *digits_p, /**< [in, } /* ecma_builtin_number_prototype_helper_round */ /** + * Size of Number toString digit buffers. + */ +#define NUMBER_TO_STRING_MAX_DIGIT_COUNT 64u + +/** * The Number.prototype object's 'toString' and 'toLocaleString' routines * * See also: @@ -176,249 +182,130 @@ ecma_builtin_number_prototype_object_to_string (ecma_number_t this_arg_number, / return ecma_make_string_value (ret_str_p); } - int buff_size = 0; - + uint8_t integer_digits[NUMBER_TO_STRING_MAX_DIGIT_COUNT]; + uint8_t fraction_digits[NUMBER_TO_STRING_MAX_DIGIT_COUNT]; + uint32_t integer_zeros = 0; + uint32_t fraction_zeros = 0; bool is_number_negative = false; + if (ecma_number_is_negative (this_arg_number)) { - /* ecma_number_to_decimal can't handle negative numbers, so we get rid of the sign. */ this_arg_number = -this_arg_number; is_number_negative = true; - - /* Add space for the sign in the result. */ - buff_size += 1; } - /* Decompose the number. */ - lit_utf8_byte_t digits[ECMA_MAX_CHARS_IN_STRINGIFIED_NUMBER]; - int32_t exponent; - lit_utf8_size_t digit_count = ecma_number_to_decimal (this_arg_number, digits, &exponent); - - /* - * The 'exponent' given by 'ecma_number_to_decimal' specifies where the decimal point is located - * compared to the first digit in 'digits'. - * For example: 120 -> '12', exp: 3 and 0.012 -> '12', exp: -1 - * We convert it to be location of the decimal point compared to the last digit of 'digits': - * 120 -> 12 * 10^1 and 0.012 -> 12 * 10^-3 - */ - exponent = exponent - (int32_t) digit_count; - - /* 'magnitude' will be the magnitude of the number in the specific radix. */ - int magnitude; - int required_digits; - if (exponent >= 0) - { - /* - * If the exponent is non-negative that means we won't have a fractional part, and can calculate - * exactly how many digits we will have. This could be done via a mathematic formula, but in rare - * cases that can cause incorrect results due to precision issues, so we use a loop instead. - */ - magnitude = 0; - ecma_number_t counter = this_arg_number; - while (counter >= radix) - { - counter /= radix; - magnitude++; - } + ecma_number_t integer_part = floor (this_arg_number); + ecma_number_t fraction_part = this_arg_number - integer_part; - /* - * The magnitude will only tell us how many digits we have after the first one, so we add one extra. - * In this case we won't be needing a radix point, so we don't need to worry about space for it. - */ - required_digits = magnitude + 1; - } - else - { - /* - * We can't know exactly how many digits we will need, since the number may be non-terminating in the - * new radix, so we will have to estimate it. We do this by first calculating how many zeros we will - * need in the specific radix before we hit a significant digit. This is calculated from the decimal - * exponent, which we negate so that we get a positive number in the end. - */ - magnitude = (int) floor ((log (10) / log (radix)) * -exponent); - - /* - * We also need to add space for significant digits. The worst case is radix == 2, since this will - * require the most digits. In this case, the upper limit to the number of significant digits we can have is - * ECMA_NUMBER_FRACTION_WIDTH + 1. This should be sufficient for any number. - */ - required_digits = magnitude + ECMA_NUMBER_FRACTION_WIDTH + 1; - - /* - * We add an exta slot for the radix point. It is also likely that we will need extra space for a - * leading zero before the radix point. It's better to add space for that here as well, even if we may not - * need it, since later we won't be able to do so. - */ - buff_size += 2; - } - - /* - * Here we normalize the number so that it is as close to 0 as possible, which will prevent us from losing - * precision in case of extreme numbers when we later split the number into integer and fractional parts. - * This has to be done in the specific radix, otherwise it messes up the result, so we use magnitude instead. - */ - if (exponent > 0) - { - for (int i = 0; i < magnitude; i++) - { - this_arg_number /= radix; - } - } - else if (exponent < 0) + uint8_t *integer_cursor_p = integer_digits + NUMBER_TO_STRING_MAX_DIGIT_COUNT; + uint8_t *fraction_cursor_p = fraction_digits; + + if (fraction_part > 0.0) { - for (int i = 0; i < magnitude; i++) + uint8_t digit; + ecma_number_t precision = (ecma_number_get_next (this_arg_number) - this_arg_number) * 0.5f; + precision = JERRY_MAX (precision, ECMA_NUMBER_MIN_VALUE); + + do { - this_arg_number *= radix; - } - } + fraction_part *= radix; + precision *= radix; - /* Split the number into an integer and a fractional part, since we have to handle them separately. */ - uint64_t whole = (uint64_t) this_arg_number; - ecma_number_t fraction = this_arg_number - (ecma_number_t) whole; + digit = (uint8_t) floor (fraction_part); - bool should_round = false; - if (!ecma_number_is_zero (fraction) && exponent >= 0) - { - /* - * If the exponent is non-negative, and we get a non-zero fractional part, that means - * the normalization might have introduced a small error, in which case we have to correct it by rounding. - * We'll add one extra significant digit which we will later use to round. - */ - required_digits += 1; - should_round = true; - } + if (digit == 0 && fraction_cursor_p == fraction_digits) + { + fraction_zeros++; + continue; + } - /* Get the total required buffer size and allocate the buffer. */ - buff_size += required_digits; - ecma_value_t ret_value; - JMEM_DEFINE_LOCAL_ARRAY (buff, buff_size, lit_utf8_byte_t); - int buff_index = 0; + JERRY_ASSERT (fraction_cursor_p < fraction_digits + NUMBER_TO_STRING_MAX_DIGIT_COUNT); + *fraction_cursor_p++ = digit; + fraction_part -= (ecma_number_t) digit; + } while (fraction_part >= precision); - /* Calculate digits for whole part. */ - while (whole > 0) - { - JERRY_ASSERT (buff_index < buff_size && buff_index < required_digits); - buff[buff_index++] = (lit_utf8_byte_t) (whole % radix); - whole /= radix; - } + /* Round to even */ + if (fraction_part > 0.5 || (fraction_part == 0.5 && (digit & 1) != 0)) + { + /* Add carry and remove overflowing trailing digits */ + while (true) + { + (*(--fraction_cursor_p))++; + + if (*fraction_cursor_p < radix) + { + /* Re-adjust cursor to point after the last significant digit */ + fraction_cursor_p++; + break; + } + + if (fraction_cursor_p == fraction_digits) + { + /* Carry overflowed to integer part */ + integer_part += 1; + break; + } + } + } - /* The digits are backwards, we need to reverse them. */ - for (int i = 0; i < buff_index / 2; i++) - { - lit_utf8_byte_t swap = buff[i]; - buff[i] = buff[buff_index - i - 1]; - buff[buff_index - i - 1] = swap; + /* Convert fraction digits to characters. */ + for (uint8_t *digit_p = fraction_digits; digit_p < fraction_cursor_p; digit_p++) + { + *digit_p = digit_chars[*digit_p]; + } } - /* - * Calculate where we have to put the radix point relative to the beginning of - * the new digits. If the exponent is non-negative this will be right after the number. - */ - int point = exponent >= 0 ? magnitude + 1 : buff_index - magnitude; - - if (point < 0) + while (ecma_number_biased_exp (ecma_number_to_binary (integer_part / radix)) + > ECMA_NUMBER_EXPONENT_BIAS + ECMA_NUMBER_FRACTION_WIDTH) { - /* - * In this case the radix point will be before the first digit, - * so we need to leave space for leading zeros. - */ - JERRY_ASSERT (exponent < 0); - required_digits += point; + integer_zeros++; + integer_part /= radix; } - JERRY_ASSERT (required_digits <= buff_size); + uint64_t integer_u64 = (uint64_t) integer_part; - /* Calculate digits for fractional part. */ - while (buff_index < required_digits) + do { - fraction *= radix; - lit_utf8_byte_t digit = (lit_utf8_byte_t) floor (fraction); + uint64_t remainder = integer_u64 % radix; + *(--integer_cursor_p) = (uint8_t) digit_chars[remainder]; - buff[buff_index++] = digit; - fraction -= (ecma_number_t) floor (fraction); - } + integer_u64 /= radix; + } while (integer_u64 > 0); - if (should_round) - { - /* Consume last digit for rounding. */ - buff_index--; - if (buff[buff_index] > radix / 2) - { - /* We should be rounding up. */ - buff[buff_index - 1]++; + const uint32_t integer_digit_count = + (uint32_t) (integer_digits + NUMBER_TO_STRING_MAX_DIGIT_COUNT - integer_cursor_p); + JERRY_ASSERT (integer_digit_count > 0); - /* Propagate carry forward in the digits. */ - for (int i = buff_index - 1; i > 0 && buff[i] >= radix; i--) - { - buff[i] = (lit_utf8_byte_t) (buff[i] - radix); - buff[i - 1]++; - } + ecma_stringbuilder_t builder = ecma_stringbuilder_create (); - if (buff[0] >= radix) - { - /* - * Carry propagated over the whole number, we need to add a new leading digit. - * We can use the place of the original rounded digit, we just need to shift everything - * right by one. - */ - memmove (buff + 1, buff, (size_t) buff_index); - buff_index++; - buff[0] = 1; - } - } + if (is_number_negative) + { + ecma_stringbuilder_append_byte (&builder, LIT_CHAR_MINUS); } - /* Remove trailing zeros. */ - while (buff_index - 1 > point && buff[buff_index - 1] == 0) + ecma_stringbuilder_append_raw (&builder, integer_cursor_p, integer_digit_count); + + while (integer_zeros--) { - buff_index--; + ecma_stringbuilder_append_byte (&builder, LIT_CHAR_0); } - /* Add leading zeros in case place of radix point is negative. */ - if (point <= 0) + if (fraction_cursor_p != fraction_digits) { - /* We will have 'point' amount of zeros after the radix point, and +1 before. */ - int zero_count = -point + 1; - memmove (buff + zero_count, buff, (size_t) buff_index); - buff_index += zero_count; + ecma_stringbuilder_append_byte (&builder, LIT_CHAR_DOT); - for (int i = 0; i < zero_count; i++) + while (fraction_zeros--) { - buff[i] = 0; + ecma_stringbuilder_append_byte (&builder, LIT_CHAR_0); } - /* We now need to place the radix point after the first zero. */ - 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 - point)); - buff[point] = '.'; - buff_index++; - } + const uint32_t fraction_digit_count = (uint32_t) (fraction_cursor_p - fraction_digits); + JERRY_ASSERT (fraction_digit_count > 0); - /* Add negative sign if necessary. */ - if (is_number_negative) - { - memmove (buff + 1, buff, (size_t) buff_index); - buff[0] = '-'; - buff_index++; + ecma_stringbuilder_append_raw (&builder, fraction_digits, fraction_digit_count); } - JERRY_ASSERT (buff_index <= buff_size); - ecma_string_t *str_p = ecma_new_ecma_string_from_utf8 (buff, (lit_utf8_size_t) buff_index); - ret_value = ecma_make_string_value (str_p); - JMEM_FINALIZE_LOCAL_ARRAY (buff); - - return ret_value; + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); } /* ecma_builtin_number_prototype_object_to_string */ /** |