|
|
|
@@ -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"
|
|
|
|
@@ -132,6 +133,11 @@ ecma_builtin_number_prototype_helper_round (lit_utf8_byte_t *digits_p, /**< [in,
|
|
|
|
|
return (lit_utf8_size_t) round_num;
|
|
|
|
|
} /* 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
|
|
|
|
|
*
|
|
|
|
@@ -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);
|
|
|
|
|
ecma_number_t integer_part = floor (this_arg_number);
|
|
|
|
|
ecma_number_t fraction_part = this_arg_number - integer_part;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
uint8_t *integer_cursor_p = integer_digits + NUMBER_TO_STRING_MAX_DIGIT_COUNT;
|
|
|
|
|
uint8_t *fraction_cursor_p = fraction_digits;
|
|
|
|
|
|
|
|
|
|
/* 'magnitude' will be the magnitude of the number in the specific radix. */
|
|
|
|
|
int magnitude;
|
|
|
|
|
int required_digits;
|
|
|
|
|
if (exponent >= 0)
|
|
|
|
|
if (fraction_part > 0.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)
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
counter /= radix;
|
|
|
|
|
magnitude++;
|
|
|
|
|
}
|
|
|
|
|
fraction_part *= radix;
|
|
|
|
|
precision *= radix;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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);
|
|
|
|
|
digit = (uint8_t) floor (fraction_part);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < magnitude; i++)
|
|
|
|
|
{
|
|
|
|
|
this_arg_number *= 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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JERRY_ASSERT (required_digits <= buff_size);
|
|
|
|
|
|
|
|
|
|
/* Calculate digits for fractional part. */
|
|
|
|
|
while (buff_index < required_digits)
|
|
|
|
|
{
|
|
|
|
|
fraction *= radix;
|
|
|
|
|
lit_utf8_byte_t digit = (lit_utf8_byte_t) floor (fraction);
|
|
|
|
|
|
|
|
|
|
buff[buff_index++] = digit;
|
|
|
|
|
fraction -= (ecma_number_t) floor (fraction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]++;
|
|
|
|
|
|
|
|
|
|
/* Propagate carry forward in the digits. */
|
|
|
|
|
for (int i = buff_index - 1; i > 0 && buff[i] >= radix; i--)
|
|
|
|
|
if (digit == 0 && fraction_cursor_p == fraction_digits)
|
|
|
|
|
{
|
|
|
|
|
buff[i] = (lit_utf8_byte_t) (buff[i] - radix);
|
|
|
|
|
buff[i - 1]++;
|
|
|
|
|
fraction_zeros++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buff[0] >= radix)
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/* Round to even */
|
|
|
|
|
if (fraction_part > 0.5 || (fraction_part == 0.5 && (digit & 1) != 0))
|
|
|
|
|
{
|
|
|
|
|
/* Add carry and remove overflowing trailing digits */
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
(*(--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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remove trailing zeros. */
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < zero_count; i++)
|
|
|
|
|
/* Convert fraction digits to characters. */
|
|
|
|
|
for (uint8_t *digit_p = fraction_digits; digit_p < fraction_cursor_p; digit_p++)
|
|
|
|
|
{
|
|
|
|
|
buff[i] = 0;
|
|
|
|
|
*digit_p = digit_chars[*digit_p];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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++)
|
|
|
|
|
while (ecma_number_biased_exp (ecma_number_to_binary (integer_part / radix))
|
|
|
|
|
> ECMA_NUMBER_EXPONENT_BIAS + ECMA_NUMBER_FRACTION_WIDTH)
|
|
|
|
|
{
|
|
|
|
|
buff[i] = digit_chars[buff[i]];
|
|
|
|
|
integer_zeros++;
|
|
|
|
|
integer_part /= radix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Place radix point to the required position. */
|
|
|
|
|
if (point < buff_index)
|
|
|
|
|
uint64_t integer_u64 = (uint64_t) integer_part;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
memmove (buff + point + 1, buff + point, (size_t) (buff_index - point));
|
|
|
|
|
buff[point] = '.';
|
|
|
|
|
buff_index++;
|
|
|
|
|
}
|
|
|
|
|
uint64_t remainder = integer_u64 % radix;
|
|
|
|
|
*(--integer_cursor_p) = (uint8_t) digit_chars[remainder];
|
|
|
|
|
|
|
|
|
|
integer_u64 /= radix;
|
|
|
|
|
} while (integer_u64 > 0);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ecma_stringbuilder_t builder = ecma_stringbuilder_create ();
|
|
|
|
|
|
|
|
|
|
/* Add negative sign if necessary. */
|
|
|
|
|
if (is_number_negative)
|
|
|
|
|
{
|
|
|
|
|
memmove (buff + 1, buff, (size_t) buff_index);
|
|
|
|
|
buff[0] = '-';
|
|
|
|
|
buff_index++;
|
|
|
|
|
ecma_stringbuilder_append_byte (&builder, LIT_CHAR_MINUS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
ecma_stringbuilder_append_raw (&builder, integer_cursor_p, integer_digit_count);
|
|
|
|
|
|
|
|
|
|
return ret_value;
|
|
|
|
|
while (integer_zeros--)
|
|
|
|
|
{
|
|
|
|
|
ecma_stringbuilder_append_byte (&builder, LIT_CHAR_0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fraction_cursor_p != fraction_digits)
|
|
|
|
|
{
|
|
|
|
|
ecma_stringbuilder_append_byte (&builder, LIT_CHAR_DOT);
|
|
|
|
|
|
|
|
|
|
while (fraction_zeros--)
|
|
|
|
|
{
|
|
|
|
|
ecma_stringbuilder_append_byte (&builder, LIT_CHAR_0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint32_t fraction_digit_count = (uint32_t) (fraction_cursor_p - fraction_digits);
|
|
|
|
|
JERRY_ASSERT (fraction_digit_count > 0);
|
|
|
|
|
|
|
|
|
|
ecma_stringbuilder_append_raw (&builder, fraction_digits, fraction_digit_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ecma_make_string_value (ecma_stringbuilder_finalize (&builder));
|
|
|
|
|
} /* ecma_builtin_number_prototype_object_to_string */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|