• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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