Skip to content

Commit 7cd9c7a

Browse files
committed
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
1 parent 017aa6b commit 7cd9c7a

File tree

2 files changed

+330
-2
lines changed

2 files changed

+330
-2
lines changed

jerry-core/ecma/builtin-objects/ecma-builtin-number-prototype.cpp

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
#include "ecma-objects.h"
2525
#include "ecma-string-object.h"
2626
#include "ecma-try-catch-macro.h"
27+
#include "fdlibm-math.h"
2728
#include "jrt.h"
29+
#include "jrt-libc-includes.h"
2830

2931
#ifndef CONFIG_ECMA_COMPACT_PROFILE_DISABLE_NUMBER_BUILTIN
3032

@@ -118,14 +120,220 @@ ecma_builtin_number_prototype_object_to_string (ecma_value_t this_arg, /**< this
118120
return ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE));
119121
}
120122

121-
if (arguments_list_len == 0)
123+
if (arguments_list_len == 0
124+
|| ecma_number_is_nan (this_arg_number)
125+
|| ecma_number_is_infinity (this_arg_number)
126+
|| ecma_number_is_zero (this_arg_number))
122127
{
123128
ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number);
124129

125130
return ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p));
126131
}
132+
else
127133
{
128-
ECMA_BUILTIN_CP_UNIMPLEMENTED (arguments_list_p);
134+
const lit_utf8_byte_t digit_chars[36] =
135+
{
136+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
137+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
138+
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
139+
'u', 'v', 'w', 'x', 'y', 'z'
140+
};
141+
142+
ecma_completion_value_t ret_value = ecma_make_empty_completion_value ();
143+
ECMA_OP_TO_NUMBER_TRY_CATCH (arg_num, arguments_list_p[0], ret_value);
144+
145+
uint32_t radix = ecma_number_to_uint32 (arg_num);
146+
147+
if (radix < 2 || radix > 36)
148+
{
149+
ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_RANGE));
150+
}
151+
else if (radix == 10)
152+
{
153+
ecma_string_t *ret_str_p = ecma_new_ecma_string_from_number (this_arg_number);
154+
155+
ret_value = ecma_make_normal_completion_value (ecma_make_string_value (ret_str_p));
156+
}
157+
else
158+
{
159+
const int precision = 53;
160+
161+
uint64_t digits;
162+
int32_t num_digits;
163+
int32_t exponent;
164+
bool is_negative = false;
165+
166+
if (ecma_number_is_negative (this_arg_number))
167+
{
168+
this_arg_number = -this_arg_number;
169+
is_negative = true;
170+
}
171+
172+
ecma_number_to_decimal (this_arg_number, &digits, &num_digits, &exponent);
173+
174+
exponent = exponent - num_digits;
175+
bool is_scale_negative = false;
176+
177+
/* Calculate the scale of the number in the specified radix. */
178+
int scale = (int) -floor ((log (10) / log (radix)) * exponent);
179+
180+
int buff_size;
181+
182+
if (is_scale_negative)
183+
{
184+
buff_size = (int) floor ((log (this_arg_number) / log (10))) + 1;
185+
}
186+
else
187+
{
188+
buff_size = scale + precision + 2;
189+
}
190+
191+
if (is_negative)
192+
{
193+
buff_size++;
194+
}
195+
196+
if (scale < 0)
197+
{
198+
is_scale_negative = true;
199+
scale = -scale;
200+
}
201+
202+
/* Normalize the number, so that it is as close to 0 exponent as possible. */
203+
for (int i = 0; i < scale; i++)
204+
{
205+
if (is_scale_negative)
206+
{
207+
this_arg_number /= (ecma_number_t) radix;
208+
}
209+
else
210+
{
211+
this_arg_number *= (ecma_number_t) radix;
212+
}
213+
}
214+
215+
uint64_t whole = (uint64_t) this_arg_number;
216+
double fraction = this_arg_number - (double) whole;
217+
218+
MEM_DEFINE_LOCAL_ARRAY (buff, buff_size, lit_utf8_byte_t);
219+
int buff_index = 0;
220+
221+
/* Calculate digits for whole part. */
222+
while (whole > 0)
223+
{
224+
buff[buff_index++] = (lit_utf8_byte_t) (whole % radix);
225+
whole /= radix;
226+
}
227+
228+
/* Calculate where we have to put the radix point. */
229+
int point = is_scale_negative ? buff_index + scale : buff_index - scale;
230+
231+
/* Reverse the digits, since they are backwards. */
232+
for (int i = 0; i < buff_index / 2; i++)
233+
{
234+
lit_utf8_byte_t swap = buff[i];
235+
buff[i] = buff[buff_index - i - 1];
236+
buff[buff_index - i - 1] = swap;
237+
}
238+
239+
bool should_round = false;
240+
/* Calculate digits for fractional part. */
241+
for (int iter_count = 0; iter_count < precision && (fraction != 0 || is_scale_negative); iter_count++)
242+
{
243+
fraction *= radix;
244+
lit_utf8_byte_t digit = (lit_utf8_byte_t) floor (fraction);
245+
246+
buff[buff_index++] = digit;
247+
fraction -= floor (fraction);
248+
249+
if (iter_count == scale && is_scale_negative)
250+
{
251+
/*
252+
* When scale is negative, that means the original number did not have a fractional part,
253+
* but by normalizing it, we introduced one. In this case, when the iteration count reaches
254+
* the scale, we already have the number, but it may be incorrect, so we calculate
255+
* one extra digit that we round off just to make sure.
256+
*/
257+
should_round = true;
258+
break;
259+
}
260+
}
261+
262+
if (should_round)
263+
{
264+
/* Round off last digit. */
265+
if (buff[buff_index - 1] > radix / 2)
266+
{
267+
buff[buff_index - 2]++;
268+
}
269+
270+
buff_index--;
271+
272+
/* Propagate carry. */
273+
for (int i = buff_index - 1; i > 0 && buff[i] >= radix; i--)
274+
{
275+
buff[i] = (lit_utf8_byte_t) (buff[i] - radix);
276+
buff[i - 1]++;
277+
}
278+
279+
/* Carry propagated over the whole number, need to add a leading digit. */
280+
if (buff[0] >= radix)
281+
{
282+
memmove (buff + 1, buff, (size_t) buff_index);
283+
buff_index++;
284+
buff[0] = 1;
285+
}
286+
}
287+
288+
/* Remove trailing zeros from fraction. */
289+
while (buff_index - 1 > point && buff[buff_index - 1] == 0)
290+
{
291+
buff_index--;
292+
}
293+
294+
/* Add leading zeros in case place of radix point is negative. */
295+
if (point <= 0)
296+
{
297+
memmove (buff - point + 1, buff, (size_t) buff_index);
298+
buff_index += -point + 1;
299+
300+
for (int i = 0; i < -point + 1; i++)
301+
{
302+
buff[i] = 0;
303+
}
304+
305+
point = 1;
306+
}
307+
308+
/* Convert digits to characters. */
309+
for (int i = 0; i < buff_index; i++)
310+
{
311+
buff[i] = digit_chars[buff[i]];
312+
}
313+
314+
/* Place radix point to the required position. */
315+
if (point < buff_index)
316+
{
317+
memmove (buff + point + 1, buff + point, (size_t) buff_index);
318+
buff[point] = '.';
319+
buff_index++;
320+
}
321+
322+
/* Add negative sign if necessary. */
323+
if (is_negative)
324+
{
325+
memmove (buff + 1, buff, (size_t) buff_index);
326+
buff_index++;
327+
buff[0] = '-';
328+
}
329+
330+
JERRY_ASSERT (buff_index <= buff_size);
331+
ecma_string_t* str_p = ecma_new_ecma_string_from_utf8 (buff, (lit_utf8_size_t) buff_index);
332+
ret_value = ecma_make_normal_completion_value (ecma_make_string_value (str_p));
333+
MEM_FINALIZE_LOCAL_ARRAY (buff);
334+
}
335+
ECMA_OP_TO_NUMBER_FINALIZE (arg_num);
336+
return ret_value;
129337
}
130338
} /* ecma_builtin_number_prototype_object_to_string */
131339

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2015 Samsung Electronics Co., Ltd.
2+
// Copyright 2015 University of Szeged.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
assert((NaN).toString() === "NaN");
17+
assert((-Infinity).toString() === "-Infinity");
18+
assert((Infinity).toString() === "Infinity");
19+
assert((NaN).toString(6) === "NaN");
20+
assert((-Infinity).toString(7) === "-Infinity");
21+
assert((Infinity).toString(8) === "Infinity");
22+
assert((16).toString(16) === "10");
23+
assert((15).toString(16) === "f");
24+
assert((12.5).toString(4) === "30.2");
25+
assert((0.005).toString(4) === "0.000110132232011013223201101323");
26+
assert((2000).toString(4) === "133100");
27+
assert((2000).toString(3) === "2202002");
28+
assert((2000).toString(16) === "7d0");
29+
assert((0.03125).toString(2) === "0.00001");
30+
assert((0.03125).toString(16) === "0.08");
31+
assert((0.0001).toString(4) === "0.000000122031232023223013010030231")
32+
assert((0).toString(16) === "0");
33+
assert((-16).toString(16) === "-10");
34+
assert((-15).toString(16) === "-f");
35+
assert((-12.5).toString(4) === "-30.2");
36+
assert((-0.005).toString(4) === "-0.000110132232011013223201101323");
37+
assert((-2000).toString(4) === "-133100");
38+
assert((-2000).toString(3) === "-2202002");
39+
assert((-2000).toString(16) === "-7d0");
40+
assert((-0.03125).toString(2) === "-0.00001");
41+
assert((-0.03125).toString(16) === "-0.08");
42+
assert((-0.0001).toString(4) === "-0.000000122031232023223013010030231")
43+
assert((-0).toString(16) === "0");
44+
45+
assert((123400).toString(2) === "11110001000001000");
46+
assert((123400).toString(3) === "20021021101");
47+
assert((123400).toString(4) === "132020020");
48+
assert((123400).toString(5) === "12422100");
49+
assert((123400).toString(6) === "2351144");
50+
assert((123400).toString(7) === "1022524");
51+
assert((123400).toString(8) === "361010");
52+
assert((123400).toString(9) === "207241");
53+
assert((123400).toString(10) === "123400");
54+
assert((123400).toString(11) === "84792");
55+
assert((123400).toString(12) === "5b4b4");
56+
assert((123400).toString(13) === "44224");
57+
assert((123400).toString(14) === "32d84");
58+
assert((123400).toString(15) === "2686a");
59+
assert((123400).toString(16) === "1e208");
60+
assert((123400).toString(17) === "181ge");
61+
assert((123400).toString(18) === "132fa");
62+
assert((123400).toString(19) === "hife");
63+
assert((123400).toString(20) === "f8a0");
64+
assert((123400).toString(21) === "d6h4");
65+
assert((123400).toString(22) === "bcl2");
66+
assert((123400).toString(23) === "a365");
67+
assert((123400).toString(24) === "8m5g");
68+
assert((123400).toString(25) === "7mb0");
69+
assert((123400).toString(26) === "70e4");
70+
assert((123400).toString(27) === "677a");
71+
assert((123400).toString(28) === "5hb4");
72+
assert((123400).toString(29) === "51l5");
73+
assert((123400).toString(30) === "4h3a");
74+
assert((123400).toString(31) === "44ck");
75+
assert((123400).toString(32) === "3og8");
76+
assert((123400).toString(33) === "3ead");
77+
assert((123400).toString(34) === "34pe");
78+
assert((123400).toString(35) === "2upp");
79+
assert((123400).toString(36) === "2n7s");
80+
81+
assert((123400).toString(new Number(16)) === "1e208");
82+
83+
var digit_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
84+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
85+
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
86+
'u', 'v', 'w', 'x', 'y', 'z'];
87+
88+
for (radix = 2; radix <= 36; radix++) {
89+
for (num = 1; num < 100; num++) {
90+
var value = num;
91+
var str = "";
92+
while (value > 0) {
93+
str = digit_chars[value % radix] + str;
94+
value = Math.floor(value / radix);
95+
}
96+
97+
assert(str === (num).toString(radix));
98+
}
99+
}
100+
101+
try {
102+
assert((123).toString(1));
103+
assert(false)
104+
} catch (e) {
105+
assert(e instanceof RangeError);
106+
}
107+
108+
try {
109+
assert((123).toString(37));
110+
assert(false)
111+
} catch (e) {
112+
assert(e instanceof RangeError);
113+
}
114+
115+
try {
116+
assert((123).toString(Infinity));
117+
assert(false)
118+
} catch (e) {
119+
assert(e instanceof RangeError);
120+
}

0 commit comments

Comments
 (0)