Implement printing in the specified radix for Number.prototype.toString().

JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai.u-szeged@partner.samsung.com
This commit is contained in:
Dániel Bátyai 2015-06-09 13:03:57 +02:00
parent c12914c71a
commit 5e0a355ab9
4 changed files with 378 additions and 50 deletions

View File

@ -579,6 +579,30 @@ typedef float ecma_number_t;
* Maximum number of significant digits that ecma-number can store
*/
#define ECMA_NUMBER_MAX_DIGITS (9)
/**
* Width of sign field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_SIGN_WIDTH (1)
/**
* Width of biased exponent field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_BIASED_EXP_WIDTH (8)
/**
* Width of fraction field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_FRACTION_WIDTH (23)
#elif CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT64
/**
* Description of an ecma-number
@ -590,6 +614,30 @@ typedef double ecma_number_t;
* Maximum number of significant digits that ecma-number can store
*/
#define ECMA_NUMBER_MAX_DIGITS (18)
/**
* Width of sign field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_SIGN_WIDTH (1)
/**
* Width of biased exponent field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_BIASED_EXP_WIDTH (11)
/**
* Width of fraction field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_FRACTION_WIDTH (52)
#endif /* CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT64 */
/**

View File

@ -27,30 +27,6 @@
#if CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT32
JERRY_STATIC_ASSERT (sizeof (ecma_number_t) == sizeof (uint32_t));
/**
* Width of sign field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_SIGN_WIDTH (1)
/**
* Width of biased exponent field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_BIASED_EXP_WIDTH (8)
/**
* Width of fraction field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_FRACTION_WIDTH (23)
/**
* Packing sign, fraction and biased exponent to ecma-number
*
@ -137,30 +113,6 @@ const ecma_number_t ecma_number_relative_eps = 1.0e-10f;
#elif CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT64
JERRY_STATIC_ASSERT (sizeof (ecma_number_t) == sizeof (uint64_t));
/**
* Width of sign field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_SIGN_WIDTH (1)
/**
* Width of biased exponent field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_BIASED_EXP_WIDTH (11)
/**
* Width of fraction field
*
* See also:
* IEEE-754 2008, 3.6, Table 3.5
*/
#define ECMA_NUMBER_FRACTION_WIDTH (52)
/**
* Packing sign, fraction and biased exponent to ecma-number
*

View File

@ -24,7 +24,9 @@
#include "ecma-objects.h"
#include "ecma-string-object.h"
#include "ecma-try-catch-macro.h"
#include "fdlibm-math.h"
#include "jrt.h"
#include "jrt-libc-includes.h"
#ifndef CONFIG_ECMA_COMPACT_PROFILE_DISABLE_NUMBER_BUILTIN
@ -118,14 +120,220 @@ ecma_builtin_number_prototype_object_to_string (ecma_value_t this_arg, /**< this
return ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE));
}
if (arguments_list_len == 0)
if (arguments_list_len == 0
|| ecma_number_is_nan (this_arg_number)
|| ecma_number_is_infinity (this_arg_number)
|| ecma_number_is_zero (this_arg_number))
{
ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number);
return ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p));
}
else
{
ECMA_BUILTIN_CP_UNIMPLEMENTED (arguments_list_p);
const lit_utf8_byte_t digit_chars[36] =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
};
ecma_completion_value_t ret_value = ecma_make_empty_completion_value ();
ECMA_OP_TO_NUMBER_TRY_CATCH (arg_num, arguments_list_p[0], ret_value);
uint32_t radix = ecma_number_to_uint32 (arg_num);
if (radix < 2 || radix > 36)
{
ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_RANGE));
}
else if (radix == 10)
{
ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number);
ret_value = ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p));
}
else
{
uint64_t digits;
int32_t num_digits;
int32_t exponent;
bool is_negative = false;
if (ecma_number_is_negative (this_arg_number))
{
this_arg_number = -this_arg_number;
is_negative = true;
}
ecma_number_to_decimal (this_arg_number, &digits, &num_digits, &exponent);
exponent = exponent - num_digits;
bool is_scale_negative = false;
/* Calculate the scale of the number in the specified radix. */
int scale = (int) -floor ((log (10) / log (radix)) * exponent);
int buff_size;
if (is_scale_negative)
{
buff_size = (int) floor ((log (this_arg_number) / log (10))) + 1;
}
else
{
buff_size = scale + ECMA_NUMBER_FRACTION_WIDTH + 2;
}
if (is_negative)
{
buff_size++;
}
if (scale < 0)
{
is_scale_negative = true;
scale = -scale;
}
/* Normalize the number, so that it is as close to 0 exponent as possible. */
for (int i = 0; i < scale; i++)
{
if (is_scale_negative)
{
this_arg_number /= (ecma_number_t) radix;
}
else
{
this_arg_number *= (ecma_number_t) radix;
}
}
uint64_t whole = (uint64_t) this_arg_number;
ecma_number_t fraction = this_arg_number - (ecma_number_t) whole;
MEM_DEFINE_LOCAL_ARRAY (buff, buff_size, lit_utf8_byte_t);
int buff_index = 0;
/* Calculate digits for whole part. */
while (whole > 0)
{
buff[buff_index++] = (lit_utf8_byte_t) (whole % radix);
whole /= radix;
}
/* Calculate where we have to put the radix point. */
int point = is_scale_negative ? buff_index + scale : buff_index - scale;
/* Reverse the digits, since they are backwards. */
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;
}
bool should_round = false;
/* Calculate digits for fractional part. */
for (int iter_count = 0;
iter_count < ECMA_NUMBER_FRACTION_WIDTH && (fraction != 0 || is_scale_negative);
iter_count++)
{
fraction *= (ecma_number_t) radix;
lit_utf8_byte_t digit = (lit_utf8_byte_t) floor (fraction);
buff[buff_index++] = digit;
fraction -= (ecma_number_t) floor (fraction);
if (iter_count == scale && is_scale_negative)
{
/*
* When scale is negative, that means the original number did not have a fractional part,
* but by normalizing it, we introduced one. In this case, when the iteration count reaches
* the scale, we already have the number, but it may be incorrect, so we calculate
* one extra digit that we round off just to make sure.
*/
should_round = true;
break;
}
}
if (should_round)
{
/* Round off last digit. */
if (buff[buff_index - 1] > radix / 2)
{
buff[buff_index - 2]++;
}
buff_index--;
/* Propagate carry. */
for (int i = buff_index - 1; i > 0 && buff[i] >= radix; i--)
{
buff[i] = (lit_utf8_byte_t) (buff[i] - radix);
buff[i - 1]++;
}
/* Carry propagated over the whole number, need to add a leading digit. */
if (buff[0] >= radix)
{
memmove (buff + 1, buff, (size_t) buff_index);
buff_index++;
buff[0] = 1;
}
}
/* Remove trailing zeros from fraction. */
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)
{
memmove (buff - point + 1, buff, (size_t) buff_index);
buff_index += -point + 1;
for (int i = 0; i < -point + 1; i++)
{
buff[i] = 0;
}
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);
buff[point] = '.';
buff_index++;
}
/* Add negative sign if necessary. */
if (is_negative)
{
memmove (buff + 1, buff, (size_t) buff_index);
buff_index++;
buff[0] = '-';
}
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_normal_completion_value (ecma_make_string_value (str_p));
MEM_FINALIZE_LOCAL_ARRAY (buff);
}
ECMA_OP_TO_NUMBER_FINALIZE (arg_num);
return ret_value;
}
} /* ecma_builtin_number_prototype_object_to_string */

View File

@ -0,0 +1,120 @@
// Copyright 2015 Samsung Electronics Co., Ltd.
// Copyright 2015 University of Szeged.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
assert((NaN).toString() === "NaN");
assert((-Infinity).toString() === "-Infinity");
assert((Infinity).toString() === "Infinity");
assert((NaN).toString(6) === "NaN");
assert((-Infinity).toString(7) === "-Infinity");
assert((Infinity).toString(8) === "Infinity");
assert((16).toString(16) === "10");
assert((15).toString(16) === "f");
assert((12.5).toString(4) === "30.2");
assert((0.005).toString(4) === "0.000110132232011013223201101323");
assert((2000).toString(4) === "133100");
assert((2000).toString(3) === "2202002");
assert((2000).toString(16) === "7d0");
assert((0.03125).toString(2) === "0.00001");
assert((0.03125).toString(16) === "0.08");
assert((0.0001).toString(4) === "0.000000122031232023223013010030231")
assert((0).toString(16) === "0");
assert((-16).toString(16) === "-10");
assert((-15).toString(16) === "-f");
assert((-12.5).toString(4) === "-30.2");
assert((-0.005).toString(4) === "-0.000110132232011013223201101323");
assert((-2000).toString(4) === "-133100");
assert((-2000).toString(3) === "-2202002");
assert((-2000).toString(16) === "-7d0");
assert((-0.03125).toString(2) === "-0.00001");
assert((-0.03125).toString(16) === "-0.08");
assert((-0.0001).toString(4) === "-0.000000122031232023223013010030231")
assert((-0).toString(16) === "0");
assert((123400).toString(2) === "11110001000001000");
assert((123400).toString(3) === "20021021101");
assert((123400).toString(4) === "132020020");
assert((123400).toString(5) === "12422100");
assert((123400).toString(6) === "2351144");
assert((123400).toString(7) === "1022524");
assert((123400).toString(8) === "361010");
assert((123400).toString(9) === "207241");
assert((123400).toString(10) === "123400");
assert((123400).toString(11) === "84792");
assert((123400).toString(12) === "5b4b4");
assert((123400).toString(13) === "44224");
assert((123400).toString(14) === "32d84");
assert((123400).toString(15) === "2686a");
assert((123400).toString(16) === "1e208");
assert((123400).toString(17) === "181ge");
assert((123400).toString(18) === "132fa");
assert((123400).toString(19) === "hife");
assert((123400).toString(20) === "f8a0");
assert((123400).toString(21) === "d6h4");
assert((123400).toString(22) === "bcl2");
assert((123400).toString(23) === "a365");
assert((123400).toString(24) === "8m5g");
assert((123400).toString(25) === "7mb0");
assert((123400).toString(26) === "70e4");
assert((123400).toString(27) === "677a");
assert((123400).toString(28) === "5hb4");
assert((123400).toString(29) === "51l5");
assert((123400).toString(30) === "4h3a");
assert((123400).toString(31) === "44ck");
assert((123400).toString(32) === "3og8");
assert((123400).toString(33) === "3ead");
assert((123400).toString(34) === "34pe");
assert((123400).toString(35) === "2upp");
assert((123400).toString(36) === "2n7s");
assert((123400).toString(new Number(16)) === "1e208");
var digit_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'];
for (radix = 2; radix <= 36; radix++) {
for (num = 1; num < 100; num++) {
var value = num;
var str = "";
while (value > 0) {
str = digit_chars[value % radix] + str;
value = Math.floor(value / radix);
}
assert(str === (num).toString(radix));
}
}
try {
assert((123).toString(1));
assert(false)
} catch (e) {
assert(e instanceof RangeError);
}
try {
assert((123).toString(37));
assert(false)
} catch (e) {
assert(e instanceof RangeError);
}
try {
assert((123).toString(Infinity));
assert(false)
} catch (e) {
assert(e instanceof RangeError);
}