1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/builtins/builtins_number.h"
17
18 #include "ecmascript/base/number_helper.h"
19 #include "ecmascript/ecma_macros.h"
20 #include "ecmascript/global_env.h"
21 #include "ecmascript/js_handle.h"
22 #include "ecmascript/js_hclass.h"
23 #include "ecmascript/js_number_format.h"
24 #include "ecmascript/js_primitive_ref.h"
25 #include "ecmascript/js_tagged_number.h"
26 #include "ecmascript/js_tagged_value-inl.h"
27 #include "ecmascript/mem/c_containers.h"
28 #include "ecmascript/object_factory.h"
29 #include "ecmascript/tagged_hash_table.h"
30
31 namespace panda::ecmascript::builtins {
32 using NumberHelper = base::NumberHelper;
33
NumberConstructor(EcmaRuntimeCallInfo * argv)34 JSTaggedValue BuiltinsNumber::NumberConstructor(EcmaRuntimeCallInfo *argv)
35 {
36 ASSERT(argv);
37 BUILTINS_API_TRACE(argv->GetThread(), Number, Constructor);
38 JSThread *thread = argv->GetThread();
39 [[maybe_unused]] EcmaHandleScope handleScope(thread);
40 JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
41 // 1. If value is present, then a , b , c.
42 // 2. Else Let n be +0.
43 JSTaggedNumber numberValue(0);
44 if (argv->GetArgsNumber() > 0) {
45 JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
46 // a. Let prim be ? ToNumeric(value).
47 JSHandle<JSTaggedValue> numericVal = JSTaggedValue::ToNumeric(thread, value);
48 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
49 // b. If Type(prim) is BigInt, let n be (ℝ(prim)).
50 if (numericVal->IsBigInt()) {
51 JSHandle<BigInt> bigNumericVal(numericVal);
52 numberValue = BigInt::BigIntToNumber(bigNumericVal);
53 } else {
54 // c. Otherwise, let n be prim.
55 numberValue = JSTaggedNumber(numericVal.GetTaggedValue());
56 }
57 }
58 // 3. If NewTarget is undefined, return n.
59 if (newTarget->IsUndefined()) {
60 return numberValue;
61 }
62 // 4. Let O be OrdinaryCreateFromConstructor(NewTarget, "%NumberPrototype%", «[[NumberData]]» ).
63 JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
64 JSHandle<JSObject> result =
65 thread->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(constructor), newTarget);
66 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
67 // 5. Set O.[[NumberData]] to n.
68 JSPrimitiveRef::Cast(*result)->SetValue(thread, numberValue);
69 // 6. Return O.
70 return result.GetTaggedValue();
71 }
72
73 // 20.1.2.2
IsFinite(EcmaRuntimeCallInfo * argv)74 JSTaggedValue BuiltinsNumber::IsFinite(EcmaRuntimeCallInfo *argv)
75 {
76 ASSERT(argv);
77 BUILTINS_API_TRACE(argv->GetThread(), Number, IsFinite);
78 JSTaggedValue msg = GetCallArg(argv, 0).GetTaggedValue();
79 // 1. If Type(number) is not Number, return false
80 // 2. If number is NaN, +infinite, or -infinite, return false
81 if (NumberHelper::IsFinite(msg)) {
82 return GetTaggedBoolean(true);
83 }
84 return GetTaggedBoolean(false);
85 }
86
87 // 20.1.2.3
IsInteger(EcmaRuntimeCallInfo * argv)88 JSTaggedValue BuiltinsNumber::IsInteger(EcmaRuntimeCallInfo *argv)
89 {
90 ASSERT(argv);
91 BUILTINS_API_TRACE(argv->GetThread(), Number, IsInteger);
92 JSThread *thread = argv->GetThread();
93 JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
94 bool result = false;
95 // 1. If Type(number) is not Number, return false.
96 // 2. If number is NaN, +infinite, or -infinite, return false
97 if (NumberHelper::IsFinite(msg.GetTaggedValue())) {
98 [[maybe_unused]] EcmaHandleScope handleScope(thread);
99 double value = JSTaggedNumber(msg.GetTaggedValue()).GetNumber();
100 // 3. Let integer be ToInteger(number).
101 JSTaggedNumber number = JSTaggedValue::ToInteger(thread, msg);
102 // 4. If integer is not equal to number, return false.
103 // 5. Otherwise, return true.
104 result = (value == number.GetNumber());
105 }
106 return GetTaggedBoolean(result);
107 }
108
109 // 20.1.2.4
IsNaN(EcmaRuntimeCallInfo * argv)110 JSTaggedValue BuiltinsNumber::IsNaN(EcmaRuntimeCallInfo *argv)
111 {
112 ASSERT(argv);
113 BUILTINS_API_TRACE(argv->GetThread(), Number, IsNaN);
114 JSTaggedValue msg = GetCallArg(argv, 0).GetTaggedValue();
115 // 1. If Type(number) is not Number, return false.
116 // 2. If number is NaN, return true.
117 if (NumberHelper::IsNaN(msg)) {
118 return GetTaggedBoolean(true);
119 }
120 // 3. Otherwise, return false.
121 return GetTaggedBoolean(false);
122 }
123
124 // 20.1.2.5
IsSafeInteger(EcmaRuntimeCallInfo * argv)125 JSTaggedValue BuiltinsNumber::IsSafeInteger(EcmaRuntimeCallInfo *argv)
126 {
127 ASSERT(argv);
128 BUILTINS_API_TRACE(argv->GetThread(), Number, IsSafeInteger);
129 JSThread *thread = argv->GetThread();
130 JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
131 bool result = false;
132 // 1. If Type(number) is not Number, return false.
133 // 2. If number is NaN, +infinite, or -infinite, return false
134 if (NumberHelper::IsFinite(msg.GetTaggedValue())) {
135 [[maybe_unused]] EcmaHandleScope handleScope(thread);
136 double value = JSTaggedNumber(msg.GetTaggedValue()).GetNumber();
137 // 3. Let integer be ToInteger(number).
138 JSTaggedNumber number = JSTaggedValue::ToInteger(thread, msg);
139 // 4. If integer is not equal to number, return false.
140 // 5. If abs(integer) ≤ 253−1, return true.
141 result = (value == number.GetNumber()) && std::abs(value) <= base::MAX_SAFE_INTEGER;
142 }
143 return GetTaggedBoolean(result);
144 }
145
146 // 18.2.4
147 // 20.1.2.12
ParseFloat(EcmaRuntimeCallInfo * argv)148 JSTaggedValue BuiltinsNumber::ParseFloat(EcmaRuntimeCallInfo *argv)
149 {
150 ASSERT(argv);
151 BUILTINS_API_TRACE(argv->GetThread(), Number, ParseFloat);
152 JSThread *thread = argv->GetThread();
153 JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
154 if (msg->IsUndefined()) {
155 return GetTaggedDouble(base::NAN_VALUE);
156 }
157 [[maybe_unused]] EcmaHandleScope handleScope(thread);
158 // 1. Let inputString be ToString(string).
159 JSHandle<EcmaString> numberString = JSTaggedValue::ToString(thread, msg);
160 // 2. ReturnIfAbrupt(inputString).
161 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
162 [[maybe_unused]] CVector<uint8_t> buf;
163 Span<const uint8_t> str = EcmaStringAccessor(numberString).ToUtf8Span(buf);
164 // 4. If neither trimmedString nor any prefix of trimmedString satisfies the syntax of a StrDecimalLiteral
165 // (see 7.1.3.1), return NaN.
166 if (NumberHelper::IsEmptyString(str.begin(), str.end())) {
167 return BuiltinsBase::GetTaggedDouble(base::NAN_VALUE);
168 }
169 double result = NumberHelper::StringToDouble(str.begin(), str.end(), 0, base::IGNORE_TRAILING);
170 return GetTaggedDouble(result);
171 }
172
173 // 18.2.5
174 // 20.1.2.13
ParseInt(EcmaRuntimeCallInfo * argv)175 JSTaggedValue BuiltinsNumber::ParseInt(EcmaRuntimeCallInfo *argv)
176 {
177 ASSERT(argv);
178 BUILTINS_API_TRACE(argv->GetThread(), Number, ParseInt);
179 JSThread *thread = argv->GetThread();
180 [[maybe_unused]] EcmaHandleScope handleScope(thread);
181 JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
182 JSHandle<JSTaggedValue> arg2 = GetCallArg(argv, 1);
183 int32_t radix = 0;
184
185 if (!arg2->IsUndefined()) {
186 // 7. Let R = ToInt32(radix).
187 radix = JSTaggedValue::ToInt32(thread, arg2);
188 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
189 }
190 // 1. Let inputString be ToString(string).
191 JSHandle<EcmaString> numberString = JSTaggedValue::ToString(thread, msg);
192 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
193 [[maybe_unused]] CVector<uint8_t> buf;
194 Span<const uint8_t> str = EcmaStringAccessor(numberString).ToUtf8Span(buf);
195
196 JSTaggedValue result = NumberHelper::StringToDoubleWithRadix(str.begin(), str.end(), radix);
197 return JSTaggedValue::TryCastDoubleToInt32(result.GetNumber());
198 }
199
200 // prototype
201 // 20.1.3.2
ToExponential(EcmaRuntimeCallInfo * argv)202 JSTaggedValue BuiltinsNumber::ToExponential(EcmaRuntimeCallInfo *argv)
203 {
204 ASSERT(argv);
205 JSThread *thread = argv->GetThread();
206 BUILTINS_API_TRACE(thread, Number, ToExponential);
207 [[maybe_unused]] EcmaHandleScope handleScope(thread);
208 // 1. Let x be ? thisNumberValue(this value).
209 JSTaggedNumber value = ThisNumberValue(thread, argv);
210 // 2. ReturnIfAbrupt(x).
211 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
212
213 // 3. Let f be ToInteger(fractionDigits).
214 JSHandle<JSTaggedValue> digits = GetCallArg(argv, 0);
215 JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digits);
216 // 5. ReturnIfAbrupt(f).
217 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
218
219 double values = value.GetNumber();
220 // 6. If x is NaN, return the String "NaN".
221 if (std::isnan(values)) {
222 return GetTaggedString(thread, "NaN");
223 }
224 // 8. If x < 0, then
225 // a. Let s be "-".
226 // b. Let x = –x.
227 // 9. If x = +infinity, then
228 // a. Return the concatenation of the Strings s and "Infinity".
229 if (!std::isfinite(values)) {
230 if (values < 0) {
231 return GetTaggedString(thread, "-Infinity");
232 }
233 return GetTaggedString(thread, "Infinity");
234 }
235
236 // 4. Assert: f is 0, when fractionDigits is undefined.
237 // 10. If f < 0 or f > 20, throw a RangeError exception
238 double fraction = digitInt.GetNumber();
239 if (digits->IsUndefined()) {
240 fraction = -1;
241 } else {
242 if (fraction < base::MIN_FRACTION || fraction > base::MAX_FRACTION) {
243 THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 0 to 100", JSTaggedValue::Exception());
244 }
245 }
246 return NumberHelper::DoubleToExponential(thread, values, static_cast<int>(fraction));
247 }
248
249 // 20.1.3.3
ToFixed(EcmaRuntimeCallInfo * argv)250 JSTaggedValue BuiltinsNumber::ToFixed(EcmaRuntimeCallInfo *argv)
251 {
252 ASSERT(argv);
253 JSThread *thread = argv->GetThread();
254 BUILTINS_API_TRACE(thread, Number, ToFixed);
255 [[maybe_unused]] EcmaHandleScope handleScope(thread);
256 // 1. Let x be ? thisNumberValue(this value).
257 JSTaggedNumber value = ThisNumberValue(thread, argv);
258 // 2. ReturnIfAbrupt(x).
259 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
260 // 3. Let f be ToInteger(fractionDigits). (If fractionDigits is undefined, this step produces the value 0).
261 JSHandle<JSTaggedValue> digitArgv = GetCallArg(argv, 0);
262 JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digitArgv);
263 if (digitArgv->IsUndefined()) {
264 digitInt = JSTaggedNumber(0);
265 }
266 // 4. ReturnIfAbrupt(f).
267 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
268
269 double digit = digitInt.GetNumber();
270 if (digit < base::MIN_FRACTION || digit > base::MAX_FRACTION) {
271 THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 0 to 100", JSTaggedValue::Exception());
272 }
273
274 // 6. If x is NaN, return the String "NaN".
275 double valueNumber = value.GetNumber();
276 if (std::isnan(valueNumber)) {
277 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
278 return globalConst->GetNanCapitalString();
279 }
280 // 9. If x 1021, then
281 // a. Let m = ToString(x).
282 const double FIRST_NO_FIXED = 1e21;
283 if (valueNumber >= FIRST_NO_FIXED) {
284 return value.ToString(thread).GetTaggedValue();
285 }
286
287 return NumberHelper::DoubleToFixed(thread, valueNumber, static_cast<int>(digit));
288 }
289
290 // 20.1.3.4
ToLocaleString(EcmaRuntimeCallInfo * argv)291 JSTaggedValue BuiltinsNumber::ToLocaleString(EcmaRuntimeCallInfo *argv)
292 {
293 ASSERT(argv);
294 JSThread *thread = argv->GetThread();
295 BUILTINS_API_TRACE(thread, Number, ToLocaleString);
296 [[maybe_unused]] EcmaHandleScope handleScope(thread);
297 // 1. Let x be ? thisNumberValue(this value).
298 JSHandle<JSTaggedValue> x(thread, ThisNumberValue(thread, argv));
299 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
300 // 2. Let numberFormat be ? Construct(%NumberFormat%, « locales, options »).
301 EcmaVM *ecmaVm = thread->GetEcmaVM();
302 JSHandle<JSFunction> ctor(ecmaVm->GetGlobalEnv()->GetNumberFormatFunction());
303 ObjectFactory *factory = ecmaVm->GetFactory();
304 JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(ctor);
305 JSHandle<JSNumberFormat> numberFormat = JSHandle<JSNumberFormat>::Cast(obj);
306 JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
307 JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
308 bool cacheable = (locales->IsUndefined() || locales->IsString()) && options->IsUndefined();
309 if (cacheable) {
310 auto numberFormatter = JSNumberFormat::GetCachedIcuNumberFormatter(thread, locales);
311 if (numberFormatter != nullptr) {
312 JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormatter, x.GetTaggedValue());
313 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
314 return result.GetTaggedValue();
315 }
316 }
317 JSNumberFormat::InitializeNumberFormat(thread, numberFormat, locales, options, cacheable);
318 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
319 if (cacheable) {
320 auto numberFormatter = JSNumberFormat::GetCachedIcuNumberFormatter(thread, locales);
321 ASSERT(numberFormatter != nullptr);
322 JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormatter, x.GetTaggedValue());
323 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
324 return result.GetTaggedValue();
325 }
326
327 // Return ? FormatNumeric(numberFormat, x).
328 JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormat, x.GetTaggedValue());
329 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
330 return result.GetTaggedValue();
331 }
332
333 // 20.1.3.5
ToPrecision(EcmaRuntimeCallInfo * argv)334 JSTaggedValue BuiltinsNumber::ToPrecision(EcmaRuntimeCallInfo *argv)
335 {
336 ASSERT(argv);
337 JSThread *thread = argv->GetThread();
338 BUILTINS_API_TRACE(thread, Number, ToPrecision);
339 [[maybe_unused]] EcmaHandleScope handleScope(thread);
340 // 1. Let x be ? thisNumberValue(this value).
341 JSTaggedNumber value = ThisNumberValue(thread, argv);
342 // 2. ReturnIfAbrupt(x).
343 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
344
345 // 3. If precision is undefined, return ToString(x).
346 JSHandle<JSTaggedValue> digitArgv = GetCallArg(argv, 0);
347 if (digitArgv->IsUndefined()) {
348 return value.ToString(thread).GetTaggedValue();
349 }
350 // 4. Let p be ToInteger(precision).
351 JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digitArgv);
352 // 5. ReturnIfAbrupt(p).
353 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
354
355 // 6. If x is NaN, return the String "NaN".
356 double valueNumber = value.GetNumber();
357 if (std::isnan(valueNumber)) {
358 return GetTaggedString(thread, "NaN");
359 }
360 // 9. If x = +infinity, then
361 // a. Return the String that is the concatenation of s and "Infinity".
362 if (!std::isfinite(valueNumber)) {
363 if (valueNumber < 0) {
364 return GetTaggedString(thread, "-Infinity");
365 }
366 return GetTaggedString(thread, "Infinity");
367 }
368
369 // If p < 1 or p > 21, throw a RangeError exception
370 double digit = digitInt.GetNumber();
371 if (digit < base::MIN_FRACTION + 1 || digit > base::MAX_FRACTION) {
372 THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 1 to 100", JSTaggedValue::Exception());
373 }
374 return NumberHelper::DoubleToPrecision(thread, valueNumber, static_cast<int>(digit));
375 }
376
377 // 20.1.3.6
ToString(EcmaRuntimeCallInfo * argv)378 JSTaggedValue BuiltinsNumber::ToString(EcmaRuntimeCallInfo *argv)
379 {
380 ASSERT(argv);
381 JSThread *thread = argv->GetThread();
382 BUILTINS_API_TRACE(thread, Number, ToString);
383 [[maybe_unused]] EcmaHandleScope handleScope(thread);
384 // 1. Let x be ? thisNumberValue(this value).
385 JSTaggedNumber value = ThisNumberValue(thread, argv);
386 // 2. ReturnIfAbrupt(x).
387 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
388
389 // 3. If radix is not present, let radixNumber be 10.
390 // 4. Else if radix is undefined, let radixNumber be 10.
391 double radix = base::DECIMAL;
392 JSHandle<JSTaggedValue> radixValue = GetCallArg(argv, 0);
393 // 5. Else let radixNumber be ToInteger(radix).
394 if (!radixValue->IsUndefined()) {
395 JSTaggedNumber radixNumber = JSTaggedValue::ToInteger(thread, radixValue);
396 // 6. ReturnIfAbrupt(x).
397 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
398 radix = radixNumber.GetNumber();
399 }
400
401 // 7. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
402 if (radix < base::MIN_RADIX || radix > base::MAX_RADIX) {
403 THROW_RANGE_ERROR_AND_RETURN(thread, "radix must be 2 to 36", JSTaggedValue::Exception());
404 }
405 // 8. If radixNumber = 10, return ToString(x).
406 if (radix == base::DECIMAL) {
407 return value.ToString(thread).GetTaggedValue();
408 }
409
410 double valueNumber = value.GetNumber();
411 // If x is NaN, return the String "NaN".
412 if (std::isnan(valueNumber)) {
413 return GetTaggedString(thread, "NaN");
414 }
415 // If x = +infinity, then
416 // Return the String that is the concatenation of s and "Infinity".
417 if (!std::isfinite(valueNumber)) {
418 if (valueNumber < 0) {
419 return GetTaggedString(thread, "-Infinity");
420 }
421 return GetTaggedString(thread, "Infinity");
422 }
423 return NumberHelper::DoubleToString(thread, valueNumber, static_cast<int>(radix));
424 }
425
426 // 20.1.3.7
ValueOf(EcmaRuntimeCallInfo * argv)427 JSTaggedValue BuiltinsNumber::ValueOf(EcmaRuntimeCallInfo *argv)
428 {
429 ASSERT(argv);
430 JSThread *thread = argv->GetThread();
431 BUILTINS_API_TRACE(thread, Number, ValueOf);
432 // 1. Let x be ? thisNumberValue(this value).
433 JSTaggedValue x = ThisNumberValue(thread, argv);
434
435 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
436 return x;
437 }
438
ThisNumberValue(JSThread * thread,EcmaRuntimeCallInfo * argv)439 JSTaggedNumber BuiltinsNumber::ThisNumberValue(JSThread *thread, EcmaRuntimeCallInfo *argv)
440 {
441 BUILTINS_API_TRACE(thread, Number, ThisNumberValue);
442 JSHandle<JSTaggedValue> value = GetThis(argv);
443 if (value->IsNumber()) {
444 return JSTaggedNumber(value.GetTaggedValue());
445 }
446 if (value->IsJSPrimitiveRef()) {
447 JSTaggedValue primitive = JSPrimitiveRef::Cast(value->GetTaggedObject())->GetValue();
448 if (primitive.IsNumber()) {
449 return JSTaggedNumber(primitive);
450 }
451 }
452 [[maybe_unused]] EcmaHandleScope handleScope(thread);
453 THROW_TYPE_ERROR_AND_RETURN(thread, "not number type", JSTaggedNumber::Exception());
454 }
455 } // namespace panda::ecmascript::builtins
456