boa_engine/builtins/math/mod.rs
1//! Boa's implementation of ECMAScript's global `Math` object.
2//!
3//! `Math` is a built-in object that has properties and methods for mathematical constants and functions. Itβs not a function object.
4//!
5//! `Math` works with the `Number` type. It doesn't work with `BigInt`.
6//!
7//! More information:
8//! - [ECMAScript reference][spec]
9//! - [MDN documentation][mdn]
10//!
11//! [spec]: https://tc39.es/ecma262/#sec-math-object
12//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
13
14use crate::{
15 Context, JsArgs, JsResult, JsString, JsValue, builtins::BuiltInObject,
16 context::intrinsics::Intrinsics, js_string, object::JsObject, property::Attribute,
17 realm::Realm, string::StaticJsStrings, symbol::JsSymbol,
18};
19
20use super::{BuiltInBuilder, IntrinsicObject};
21
22#[cfg(test)]
23mod tests;
24
25/// Javascript `Math` object.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub(crate) struct Math;
28
29impl IntrinsicObject for Math {
30 fn init(realm: &Realm) {
31 let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
32 let builder = BuiltInBuilder::with_intrinsic::<Self>(realm)
33 .static_property(js_string!("E"), std::f64::consts::E, attribute)
34 .static_property(js_string!("LN10"), std::f64::consts::LN_10, attribute)
35 .static_property(js_string!("LN2"), std::f64::consts::LN_2, attribute)
36 .static_property(js_string!("LOG10E"), std::f64::consts::LOG10_E, attribute)
37 .static_property(js_string!("LOG2E"), std::f64::consts::LOG2_E, attribute)
38 .static_property(js_string!("PI"), std::f64::consts::PI, attribute)
39 .static_property(
40 js_string!("SQRT1_2"),
41 std::f64::consts::FRAC_1_SQRT_2,
42 attribute,
43 )
44 .static_property(js_string!("SQRT2"), std::f64::consts::SQRT_2, attribute)
45 .static_method(Self::abs, js_string!("abs"), 1)
46 .static_method(Self::acos, js_string!("acos"), 1)
47 .static_method(Self::acosh, js_string!("acosh"), 1)
48 .static_method(Self::asin, js_string!("asin"), 1)
49 .static_method(Self::asinh, js_string!("asinh"), 1)
50 .static_method(Self::atan, js_string!("atan"), 1)
51 .static_method(Self::atanh, js_string!("atanh"), 1)
52 .static_method(Self::atan2, js_string!("atan2"), 2)
53 .static_method(Self::cbrt, js_string!("cbrt"), 1)
54 .static_method(Self::ceil, js_string!("ceil"), 1)
55 .static_method(Self::clz32, js_string!("clz32"), 1)
56 .static_method(Self::cos, js_string!("cos"), 1)
57 .static_method(Self::cosh, js_string!("cosh"), 1)
58 .static_method(Self::exp, js_string!("exp"), 1)
59 .static_method(Self::expm1, js_string!("expm1"), 1)
60 .static_method(Self::floor, js_string!("floor"), 1)
61 .static_method(Self::fround, js_string!("fround"), 1)
62 .static_method(Self::hypot, js_string!("hypot"), 2)
63 .static_method(Self::imul, js_string!("imul"), 2)
64 .static_method(Self::log, js_string!("log"), 1)
65 .static_method(Self::log1p, js_string!("log1p"), 1)
66 .static_method(Self::log10, js_string!("log10"), 1)
67 .static_method(Self::log2, js_string!("log2"), 1)
68 .static_method(Self::max, js_string!("max"), 2)
69 .static_method(Self::min, js_string!("min"), 2)
70 .static_method(Self::pow, js_string!("pow"), 2)
71 .static_method(Self::random, js_string!("random"), 0)
72 .static_method(Self::round, js_string!("round"), 1)
73 .static_method(Self::sign, js_string!("sign"), 1)
74 .static_method(Self::sin, js_string!("sin"), 1)
75 .static_method(Self::sinh, js_string!("sinh"), 1)
76 .static_method(Self::sqrt, js_string!("sqrt"), 1)
77 .static_method(Self::tan, js_string!("tan"), 1)
78 .static_method(Self::tanh, js_string!("tanh"), 1)
79 .static_method(Self::trunc, js_string!("trunc"), 1)
80 .static_property(
81 JsSymbol::to_string_tag(),
82 Self::NAME,
83 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
84 );
85
86 #[cfg(feature = "float16")]
87 let builder = builder.static_method(Self::f16round, js_string!("f16round"), 1);
88
89 #[cfg(feature = "xsum")]
90 let builder = builder.static_method(Self::sum_precise, js_string!("sumPrecise"), 1);
91
92 builder.build();
93 }
94
95 fn get(intrinsics: &Intrinsics) -> JsObject {
96 intrinsics.objects().math()
97 }
98}
99
100impl BuiltInObject for Math {
101 const NAME: JsString = StaticJsStrings::MATH;
102}
103
104impl Math {
105 /// Get the absolute value of a number.
106 ///
107 /// More information:
108 /// - [ECMAScript reference][spec]
109 /// - [MDN documentation][mdn]
110 ///
111 /// [spec]: https://tc39.es/ecma262/#sec-math.abs
112 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
113 pub(crate) fn abs(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
114 Ok(args
115 .get_or_undefined(0)
116 // 1. Let n be ? ToNumber(x).
117 .to_number(context)?
118 // 3. If n is -0π½, return +0π½.
119 // 2. If n is NaN, return NaN.
120 // 4. If n is -βπ½, return +βπ½.
121 // 5. If n < +0π½, return -n.
122 // 6. Return n.
123 .abs()
124 .into())
125 }
126
127 /// Get the arccos of a number.
128 ///
129 /// More information:
130 /// - [ECMAScript reference][spec]
131 /// - [MDN documentation][mdn]
132 ///
133 /// [spec]: https://tc39.es/ecma262/#sec-math.acos
134 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
135 pub(crate) fn acos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
136 Ok(args
137 .get_or_undefined(0)
138 // 1. Let n be ? ToNumber(x).
139 .to_number(context)?
140 // 2. If n is NaN, n > 1π½, or n < -1π½, return NaN.
141 // 3. If n is 1π½, return +0π½.
142 // 4. Return an implementation-approximated value representing the result of the inverse cosine of β(n).
143 .acos()
144 .into())
145 }
146
147 /// Get the hyperbolic arccos of a number.
148 ///
149 /// More information:
150 /// - [ECMAScript reference][spec]
151 /// - [MDN documentation][mdn]
152 ///
153 /// [spec]: https://tc39.es/ecma262/#sec-math.acosh
154 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh
155 pub(crate) fn acosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
156 // 1/βf64::EPSILON, as established by the Boost math library.
157 const ACOSH_LARGE_INPUT_THRESHOLD: f64 = 67_108_864.0;
158
159 // 1. Let n be ? ToNumber(x).
160 let n = args.get_or_undefined(0).to_number(context)?;
161 if n.is_finite() && n > ACOSH_LARGE_INPUT_THRESHOLD {
162 return Ok((n.ln() + std::f64::consts::LN_2).into());
163 }
164
165 // 4. If n < 1π½, return NaN.
166 // 2. If n is NaN or n is +βπ½, return n.
167 // 3. If n is 1π½, return +0π½.
168 // 5. Return an implementation-approximated value representing the result of the inverse hyperbolic cosine of β(n).
169 Ok(n.acosh().into())
170 }
171
172 /// Get the arcsine of a number.
173 ///
174 /// More information:
175 /// - [ECMAScript reference][spec]
176 /// - [MDN documentation][mdn]
177 ///
178 /// [spec]: https://tc39.es/ecma262/#sec-math.asin
179 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
180 pub(crate) fn asin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
181 Ok(args
182 .get_or_undefined(0)
183 // 1. Let n be ? ToNumber(x).
184 .to_number(context)?
185 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
186 // 3. If n > 1π½ or n < -1π½, return NaN.
187 // 4. Return an implementation-approximated value representing the result of the inverse sine of β(n).
188 .asin()
189 .into())
190 }
191
192 /// Get the hyperbolic arcsine of a number.
193 ///
194 /// More information:
195 /// - [ECMAScript reference][spec]
196 /// - [MDN documentation][mdn]
197 ///
198 /// [spec]: https://tc39.es/ecma262/#sec-math.asinh
199 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh
200 pub(crate) fn asinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
201 // 1/βf64::EPSILON, as established by the Boost math library.
202 const ASINH_LARGE_INPUT_THRESHOLD: f64 = 67_108_864.0;
203
204 // 1. Let n be ? ToNumber(x).
205 let n = args.get_or_undefined(0).to_number(context)?;
206 if n.is_finite() && n.abs() > ASINH_LARGE_INPUT_THRESHOLD {
207 return Ok((n.signum()
208 * (n.abs().ln() + std::f64::consts::LN_2 + 0.25 * n.recip() * n.recip()))
209 .into());
210 }
211
212 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
213 // 3. Return an implementation-approximated value representing the result of the inverse hyperbolic sine of β(n).
214 Ok(n.asinh().into())
215 }
216
217 /// Get the arctangent of a number.
218 ///
219 /// More information:
220 /// - [ECMAScript reference][spec]
221 /// - [MDN documentation][mdn]
222 ///
223 /// [spec]: https://tc39.es/ecma262/#sec-math.atan
224 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
225 pub(crate) fn atan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
226 Ok(args
227 .get_or_undefined(0)
228 // 1. Let n be ? ToNumber(x).
229 .to_number(context)?
230 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
231 // 3. If n is +βπ½, return an implementation-approximated value representing Ο / 2.
232 // 4. If n is -βπ½, return an implementation-approximated value representing -Ο / 2.
233 // 5. Return an implementation-approximated value representing the result of the inverse tangent of β(n).
234 .atan()
235 .into())
236 }
237
238 /// Get the hyperbolic arctangent of a number.
239 ///
240 /// More information:
241 /// - [ECMAScript reference][spec]
242 /// - [MDN documentation][mdn]
243 ///
244 /// [spec]: https://tc39.es/ecma262/#sec-math.atanh
245 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh
246 pub(crate) fn atanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
247 Ok(args
248 .get_or_undefined(0)
249 // 1. Let n be ? ToNumber(x).
250 .to_number(context)?
251 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
252 // 3. If n > 1π½ or n < -1π½, return NaN.
253 // 4. If n is 1π½, return +βπ½.
254 // 5. If n is -1π½, return -βπ½.
255 // 6. Return an implementation-approximated value representing the result of the inverse hyperbolic tangent of β(n).
256 .atanh()
257 .into())
258 }
259
260 /// Get the four quadrant arctangent of the quotient y / x.
261 ///
262 /// More information:
263 /// - [ECMAScript reference][spec]
264 /// - [MDN documentation][mdn]
265 ///
266 /// [spec]: https://tc39.es/ecma262/#sec-math.atan2
267 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
268 pub(crate) fn atan2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
269 // 1. Let ny be ? ToNumber(y).
270 let y = args.get_or_undefined(0).to_number(context)?;
271
272 // 2. Let nx be ? ToNumber(x).
273 let x = args.get_or_undefined(1).to_number(context)?;
274
275 // 4. If ny is +βπ½, then
276 // a. If nx is +βπ½, return an implementation-approximated value representing Ο / 4.
277 // b. If nx is -βπ½, return an implementation-approximated value representing 3Ο / 4.
278 // c. Return an implementation-approximated value representing Ο / 2.
279 // 5. If ny is -βπ½, then
280 // a. If nx is +βπ½, return an implementation-approximated value representing -Ο / 4.
281 // b. If nx is -βπ½, return an implementation-approximated value representing -3Ο / 4.
282 // c. Return an implementation-approximated value representing -Ο / 2.
283 // 6. If ny is +0π½, then
284 // a. If nx > +0π½ or nx is +0π½, return +0π½.
285 // b. Return an implementation-approximated value representing Ο.
286 // 7. If ny is -0π½, then
287 // a. If nx > +0π½ or nx is +0π½, return -0π½.
288 // b. Return an implementation-approximated value representing -Ο.
289 // 8. Assert: ny is finite and is neither +0π½ nor -0π½.
290 // 9. If ny > +0π½, then
291 // a. If nx is +βπ½, return +0π½.
292 // b. If nx is -βπ½, return an implementation-approximated value representing Ο.
293 // c. If nx is +0π½ or nx is -0π½, return an implementation-approximated value representing Ο / 2.
294 // 10. If ny < +0π½, then
295 // a. If nx is +βπ½, return -0π½.
296 // b. If nx is -βπ½, return an implementation-approximated value representing -Ο.
297 // c. If nx is +0π½ or nx is -0π½, return an implementation-approximated value representing -Ο / 2.
298 // 11. Assert: nx is finite and is neither +0π½ nor -0π½.
299 // 12. Return an implementation-approximated value representing the result of the inverse tangent of the quotient β(ny) / β(nx).
300 Ok(y.atan2(x).into())
301 }
302
303 /// Get the cubic root of a number.
304 ///
305 /// More information:
306 /// - [ECMAScript reference][spec]
307 /// - [MDN documentation][mdn]
308 ///
309 /// [spec]: https://tc39.es/ecma262/#sec-math.cbrt
310 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt
311 pub(crate) fn cbrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
312 Ok(args
313 .get_or_undefined(0)
314 // 1. Let n be ? ToNumber(x).
315 .to_number(context)?
316 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
317 // 3. Return an implementation-approximated value representing the result of the cube root of β(n).
318 .cbrt()
319 .into())
320 }
321
322 /// Get lowest integer above a number.
323 ///
324 /// More information:
325 /// - [ECMAScript reference][spec]
326 /// - [MDN documentation][mdn]
327 ///
328 /// [spec]: https://tc39.es/ecma262/#sec-math.ceil
329 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
330 pub(crate) fn ceil(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
331 Ok(args
332 .get_or_undefined(0)
333 // 1. Let n be ? ToNumber(x).
334 .to_number(context)?
335 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
336 // 3. If n < +0π½ and n > -1π½, return -0π½.
337 // 4. If n is an integral Number, return n.
338 // 5. Return the smallest (closest to -β) integral Number value that is not less than n.
339 .ceil()
340 .into())
341 }
342
343 /// Get the number of leading zeros in the 32 bit representation of a number
344 ///
345 /// More information:
346 /// - [ECMAScript reference][spec]
347 /// - [MDN documentation][mdn]
348 ///
349 /// [spec]: https://tc39.es/ecma262/#sec-math.clz32
350 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
351 pub(crate) fn clz32(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
352 Ok(args
353 .get_or_undefined(0)
354 // 1. Let n be ? ToUint32(x).
355 .to_u32(context)?
356 // 2. Let p be the number of leading zero bits in the unsigned 32-bit binary representation of n.
357 // 3. Return π½(p).
358 .leading_zeros()
359 .into())
360 }
361
362 /// Get the cosine of a number.
363 ///
364 /// More information:
365 /// - [ECMAScript reference][spec]
366 /// - [MDN documentation][mdn]
367 ///
368 /// [spec]: https://tc39.es/ecma262/#sec-math.cos
369 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
370 pub(crate) fn cos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
371 Ok(args
372 .get_or_undefined(0)
373 // 1. Let n be ? ToNumber(x).
374 .to_number(context)?
375 // 2. If n is NaN, n is +βπ½, or n is -βπ½, return NaN.
376 // 3. If n is +0π½ or n is -0π½, return 1π½.
377 // 4. Return an implementation-approximated value representing the result of the cosine of β(n).
378 .cos()
379 .into())
380 }
381
382 /// Get the hyperbolic cosine of a number.
383 ///
384 /// More information:
385 /// - [ECMAScript reference][spec]
386 /// - [MDN documentation][mdn]
387 ///
388 /// [spec]: https://tc39.es/ecma262/#sec-math.cosh
389 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh
390 pub(crate) fn cosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
391 Ok(args
392 .get_or_undefined(0)
393 // 1. Let n be ? ToNumber(x).
394 .to_number(context)?
395 // 2. If n is NaN, return NaN.
396 // 3. If n is +βπ½ or n is -βπ½, return +βπ½.
397 // 4. If n is +0π½ or n is -0π½, return 1π½.
398 // 5. Return an implementation-approximated value representing the result of the hyperbolic cosine of β(n).
399 .cosh()
400 .into())
401 }
402
403 /// Get the power to raise the natural logarithm to get the number.
404 ///
405 /// More information:
406 /// - [ECMAScript reference][spec]
407 /// - [MDN documentation][mdn]
408 ///
409 /// [spec]: https://tc39.es/ecma262/#sec-math.exp
410 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
411 pub(crate) fn exp(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
412 Ok(args
413 .get_or_undefined(0)
414 // 1. Let n be ? ToNumber(x).
415 .to_number(context)?
416 // 2. If n is NaN or n is +βπ½, return n.
417 // 3. If n is +0π½ or n is -0π½, return 1π½.
418 // 4. If n is -βπ½, return +0π½.
419 // 5. Return an implementation-approximated value representing the result of the exponential function of β(n).
420 .exp()
421 .into())
422 }
423
424 /// The `Math.expm1()` function returns e^x - 1, where x is the argument, and e the base of
425 /// the natural logarithms. The result is computed in a way that is accurate even when the
426 /// value of x is close 0
427 ///
428 /// More information:
429 /// - [ECMAScript reference][spec]
430 /// - [MDN documentation][mdn]
431 ///
432 /// [spec]: https://tc39.es/ecma262/#sec-math.expm1
433 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1
434 pub(crate) fn expm1(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
435 Ok(args
436 .get_or_undefined(0)
437 // 1. Let n be ? ToNumber(x).
438 .to_number(context)?
439 // 2. If n is NaN, n is +0π½, n is -0π½, or n is +βπ½, return n.
440 // 3. If n is -βπ½, return -1π½.
441 // 4. Return an implementation-approximated value representing the result of subtracting 1 from the exponential function of β(n).
442 .exp_m1()
443 .into())
444 }
445
446 /// Get the highest integer below a number.
447 ///
448 /// More information:
449 /// - [ECMAScript reference][spec]
450 /// - [MDN documentation][mdn]
451 ///
452 /// [spec]: https://tc39.es/ecma262/#sec-math.floor
453 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
454 pub(crate) fn floor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
455 Ok(args
456 .get_or_undefined(0)
457 // 1. Let n be ? ToNumber(x).
458 .to_number(context)?
459 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
460 // 3. If n < 1π½ and n > +0π½, return +0π½.
461 // 4. If n is an integral Number, return n.
462 // 5. Return the greatest (closest to +β) integral Number value that is not greater than n.
463 .floor()
464 .into())
465 }
466
467 /// The `Math.f16round()` static method returns the nearest 16-bit half precision
468 /// float representation of a number.
469 ///
470 /// More information:
471 /// - [ECMAScript reference][spec]
472 /// - [MDN documentation][mdn]
473 ///
474 /// [spec]: https://tc39.es/ecma262/#sec-math.round
475 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/f16round
476 #[allow(clippy::float_cmp)]
477 #[cfg(feature = "float16")]
478 pub(crate) fn f16round(
479 _: &JsValue,
480 args: &[JsValue],
481 context: &mut Context,
482 ) -> JsResult<JsValue> {
483 let num = args
484 .get_or_undefined(0)
485 //1. Let n be ? ToNumber(x).
486 .to_number(context)?;
487
488 // 2. If n is NaN, return NaN.
489 // 3. If n is one of +0π½, -0π½, +βπ½, or -βπ½, return n.
490 // 4. Let n16 be the result of converting n to IEEE 754-2019 binary16 format using roundTiesToEven mode.
491 // 5. Let n64 be the result of converting n16 to IEEE 754-2019 binary64 format.
492 // 6. Return the ECMAScript Number value corresponding to n64.
493 Ok(float16::f16::from_f64(num).to_f64().into())
494 }
495
496 /// Get the nearest 32-bit single precision float representation of a number.
497 ///
498 /// More information:
499 /// - [ECMAScript reference][spec]
500 /// - [MDN documentation][mdn]
501 ///
502 /// [spec]: https://tc39.es/ecma262/#sec-math.fround
503 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround
504 pub(crate) fn fround(
505 _: &JsValue,
506 args: &[JsValue],
507 context: &mut Context,
508 ) -> JsResult<JsValue> {
509 // 1. Let n be ? ToNumber(x).
510 let x = args.get_or_undefined(0).to_number(context)?;
511
512 // 2. If n is NaN, return NaN.
513 // 3. If n is one of +0π½, -0π½, +βπ½, or -βπ½, return n.
514 // 4. Let n32 be the result of converting n to a value in IEEE 754-2019 binary32 format using roundTiesToEven mode.
515 // 5. Let n64 be the result of converting n32 to a value in IEEE 754-2019 binary64 format.
516 // 6. Return the ECMAScript Number value corresponding to n64.
517 Ok(f64::from(x as f32).into())
518 }
519
520 /// Get an approximation of the square root of the sum of squares of all arguments.
521 ///
522 /// More information:
523 /// - [ECMAScript reference][spec]
524 /// - [MDN documentation][mdn]
525 ///
526 /// [spec]: https://tc39.es/ecma262/#sec-math.hypot
527 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
528 pub(crate) fn hypot(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
529 // 1. Let coerced be a new empty List.
530 // 2. For each element arg of args, do
531 // a. Let n be ? ToNumber(arg).
532 // b. Append n to coerced.
533 // 3. For each element number of coerced, do
534 // 5. For each element number of coerced, do
535 let mut result = 0f64;
536 for arg in args {
537 let num = arg.to_number(context)?;
538 // a. If number is +βπ½ or number is -βπ½, return +βπ½.
539 // 4. Let onlyZero be true.
540 // a. If number is NaN, return NaN.
541 // b. If number is neither +0π½ nor -0π½, set onlyZero to false.
542 // 6. If onlyZero is true, return +0π½.
543 // 7. Return an implementation-approximated value representing the square root of the sum of squares of the mathematical values of the elements of coerced.
544 result = result.hypot(num);
545 }
546
547 Ok(result.into())
548 }
549
550 /// Get the result of the C-like 32-bit multiplication of the two parameters.
551 ///
552 /// More information:
553 /// - [ECMAScript reference][spec]
554 /// - [MDN documentation][mdn]
555 ///
556 /// [spec]: https://tc39.es/ecma262/#sec-math.imul
557 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
558 pub(crate) fn imul(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
559 // 1. Let a be β(? ToUint32(x)).
560 let x = args.get_or_undefined(0).to_u32(context)?;
561
562 // 2. Let b be β(? ToUint32(y)).
563 let y = args.get_or_undefined(1).to_u32(context)?;
564
565 // 3. Let product be (a Γ b) modulo 2^32.
566 // 4. If product β₯ 2^31, return π½(product - 2^32); otherwise return π½(product).
567 Ok((x.wrapping_mul(y) as i32).into())
568 }
569
570 /// Get the natural logarithm of a number.
571 ///
572 /// More information:
573 /// - [ECMAScript reference][spec]
574 /// - [MDN documentation][mdn]
575 ///
576 /// [spec]: https://tc39.es/ecma262/#sec-math.log
577 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
578 pub(crate) fn log(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
579 Ok(args
580 .get_or_undefined(0)
581 // 1. Let n be ? ToNumber(x).
582 .to_number(context)?
583 // 2. If n is NaN or n is +βπ½, return n.
584 // 3. If n is 1π½, return +0π½.
585 // 4. If n is +0π½ or n is -0π½, return -βπ½.
586 // 5. If n < +0π½, return NaN.
587 // 6. Return an implementation-approximated value representing the result of the natural logarithm of β(n).
588 .ln()
589 .into())
590 }
591
592 /// Get approximation to the natural logarithm of 1 + x.
593 ///
594 /// More information:
595 /// - [ECMAScript reference][spec]
596 /// - [MDN documentation][mdn]
597 ///
598 /// [spec]: https://tc39.es/ecma262/#sec-math.log1p
599 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p
600 pub(crate) fn log1p(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
601 Ok(args
602 .get_or_undefined(0)
603 // 1. Let n be ? ToNumber(x).
604 .to_number(context)?
605 // 2. If n is NaN, n is +0π½, n is -0π½, or n is +βπ½, return n.
606 // 3. If n is -1π½, return -βπ½.
607 // 4. If n < -1π½, return NaN.
608 // 5. Return an implementation-approximated value representing the result of the natural logarithm of 1 + β(n).
609 .ln_1p()
610 .into())
611 }
612
613 /// Get the base 10 logarithm of the number.
614 ///
615 /// More information:
616 /// - [ECMAScript reference][spec]
617 /// - [MDN documentation][mdn]
618 ///
619 /// [spec]: https://tc39.es/ecma262/#sec-math.log10
620 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10
621 pub(crate) fn log10(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
622 Ok(args
623 .get_or_undefined(0)
624 // 1. Let n be ? ToNumber(x).
625 .to_number(context)?
626 // 2. If n is NaN or n is +βπ½, return n.
627 // 3. If n is 1π½, return +0π½.
628 // 4. If n is +0π½ or n is -0π½, return -βπ½.
629 // 5. If n < +0π½, return NaN.
630 // 6. Return an implementation-approximated value representing the result of the base 10 logarithm of β(n).
631 .log10()
632 .into())
633 }
634
635 /// Get the base 2 logarithm of the number.
636 ///
637 /// More information:
638 /// - [ECMAScript reference][spec]
639 /// - [MDN documentation][mdn]
640 ///
641 /// [spec]: https://tc39.es/ecma262/#sec-math.log2
642 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2
643 pub(crate) fn log2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
644 Ok(args
645 .get_or_undefined(0)
646 // 1. Let n be ? ToNumber(x).
647 .to_number(context)?
648 // 2. If n is NaN or n is +βπ½, return n.
649 // 3. If n is 1π½, return +0π½.
650 // 4. If n is +0π½ or n is -0π½, return -βπ½.
651 // 5. If n < +0π½, return NaN.
652 // 6. Return an implementation-approximated value representing the result of the base 2 logarithm of β(n).
653 .log2()
654 .into())
655 }
656
657 /// Get the maximum of several numbers.
658 ///
659 /// More information:
660 /// - [ECMAScript reference][spec]
661 /// - [MDN documentation][mdn]
662 ///
663 /// [spec]: https://tc39.es/ecma262/#sec-math.max
664 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
665 pub(crate) fn max(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
666 // 1. Let coerced be a new empty List.
667 // 2. For each element arg of args, do
668 // b. Append n to coerced.
669 // 3. Let highest be -βπ½.
670 let mut highest = f64::NEG_INFINITY;
671
672 // 4. For each element number of coerced, do
673 for arg in args {
674 // a. Let n be ? ToNumber(arg).
675 let num = arg.to_number(context)?;
676
677 highest = if highest.is_nan() {
678 continue;
679 } else if num.is_nan() {
680 // a. If number is NaN, return NaN.
681 f64::NAN
682 } else {
683 match (highest, num) {
684 // b. When x and y are +0π½ -0π½, return +0π½.
685 (x, y) if x == 0f64 && y == 0f64 && x.signum() != y.signum() => 0f64,
686 // c. Otherwise, return the maximum value.
687 (x, y) => x.max(y),
688 }
689 };
690 }
691 // 5. Return highest.
692 Ok(highest.into())
693 }
694
695 /// Get the minimum of several numbers.
696 ///
697 /// More information:
698 /// - [ECMAScript reference][spec]
699 /// - [MDN documentation][mdn]
700 ///
701 /// [spec]: https://tc39.es/ecma262/#sec-math.min
702 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
703 pub(crate) fn min(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
704 // 1. Let coerced be a new empty List.
705 // 2. For each element arg of args, do
706 // b. Append n to coerced.
707 // 3. Let lowest be +βπ½.
708 let mut lowest = f64::INFINITY;
709
710 // 4. For each element number of coerced, do
711 for arg in args {
712 // a. Let n be ? ToNumber(arg).
713 let num = arg.to_number(context)?;
714
715 lowest = if lowest.is_nan() {
716 continue;
717 } else if num.is_nan() {
718 // a. If number is NaN, return NaN.
719 f64::NAN
720 } else {
721 match (lowest, num) {
722 // b. When x and y are +0π½ -0π½, return -0π½.
723 (x, y) if x == 0f64 && y == 0f64 && x.signum() != y.signum() => -0f64,
724 // c. Otherwise, return the minimum value.
725 (x, y) => x.min(y),
726 }
727 };
728 }
729 // 5. Return lowest.
730 Ok(lowest.into())
731 }
732
733 /// Raise a number to a power.
734 ///
735 /// More information:
736 /// - [ECMAScript reference][spec]
737 /// - [MDN documentation][mdn]
738 ///
739 /// [spec]: https://tc39.es/ecma262/#sec-math.pow
740 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
741 #[allow(clippy::float_cmp)]
742 pub(crate) fn pow(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
743 // 1. Set base to ? ToNumber(base).
744 let x = args.get_or_undefined(0).to_number(context)?;
745
746 // 2. Set exponent to ? ToNumber(exponent).
747 let y = args.get_or_undefined(1).to_number(context)?;
748
749 // 3. Return Number::exponentiate(base, exponent).
750
751 // https://github.com/rust-lang/rust/issues/60468
752
753 // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate
754 // 6.1.6.1.3 Number::exponentiate ( base, exponent )
755 // 1. If exponent is NaN, return NaN.
756 if y.is_nan() {
757 return Ok(f64::NAN.into());
758 }
759
760 // 9. If exponent is +βπ½, then
761 // a. If abs(β(base)) > 1, return +βπ½.
762 // b. If abs(β(base)) = 1, return NaN.
763 // c. If abs(β(base)) < 1, return +0π½.
764 // 10. If exponent is -βπ½, then
765 // a. If abs(β(base)) > 1, return +0π½.
766 // b. If abs(β(base)) = 1, return NaN.
767 // c. If abs(β(base)) < 1, return +βπ½.
768 if f64::abs(x) == 1f64 && y.is_infinite() {
769 return Ok(f64::NAN.into());
770 }
771
772 Ok(x.powf(y).into())
773 }
774
775 /// Generate a random floating-point number between `0` and `1`.
776 ///
777 /// More information:
778 /// - [ECMAScript reference][spec]
779 /// - [MDN documentation][mdn]
780 ///
781 /// [spec]: https://tc39.es/ecma262/#sec-math.random
782 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
783 #[allow(clippy::unnecessary_wraps)]
784 pub(crate) fn random(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
785 // NOTE: Each Math.random function created for distinct realms must produce a distinct sequence of values from successive calls.
786 Ok(rand::random::<f64>().into())
787 }
788
789 /// Round a number to the nearest integer.
790 ///
791 /// More information:
792 /// - [ECMAScript reference][spec]
793 /// - [MDN documentation][mdn]
794 ///
795 /// [spec]: https://tc39.es/ecma262/#sec-math.round
796 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
797 #[allow(clippy::float_cmp)]
798 pub(crate) fn round(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
799 let num = args
800 .get_or_undefined(0)
801 //1. Let n be ? ToNumber(x).
802 .to_number(context)?;
803
804 //2. If n is NaN, +βπ½, -βπ½, or an integral Number, return n.
805 //3. If n < 0.5π½ and n > +0π½, return +0π½.
806 //4. If n < +0π½ and n β₯ -0.5π½, return -0π½.
807 //5. Return the integral Number closest to n, preferring the Number closer to +β in the case of a tie.
808
809 if num.fract() == -0.5 {
810 Ok(num.ceil().into())
811 } else {
812 Ok(num.round().into())
813 }
814 }
815
816 /// Get the sign of a number.
817 ///
818 /// More information:
819 /// - [ECMAScript reference][spec]
820 /// - [MDN documentation][mdn]
821 ///
822 /// [spec]: https://tc39.es/ecma262/#sec-math.sign
823 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
824 pub(crate) fn sign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
825 // 1. Let n be ? ToNumber(x).
826 let n = args.get_or_undefined(0).to_number(context)?;
827
828 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
829 if n == 0f64 {
830 return Ok(n.into());
831 }
832 // 3. If n < +0π½, return -1π½.
833 // 4. Return 1π½.
834 Ok(n.signum().into())
835 }
836
837 /// Get the sine of a number.
838 ///
839 /// More information:
840 /// - [ECMAScript reference][spec]
841 /// - [MDN documentation][mdn]
842 ///
843 /// [spec]: https://tc39.es/ecma262/#sec-math.sin
844 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
845 pub(crate) fn sin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
846 Ok(args
847 .get_or_undefined(0)
848 // 1. Let n be ? ToNumber(x).
849 .to_number(context)?
850 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
851 // 3. If n is +βπ½ or n is -βπ½, return NaN.
852 // 4. Return an implementation-approximated value representing the result of the sine of β(n).
853 .sin()
854 .into())
855 }
856
857 /// Get the hyperbolic sine of a number.
858 ///
859 /// More information:
860 /// - [ECMAScript reference][spec]
861 /// - [MDN documentation][mdn]
862 ///
863 /// [spec]: https://tc39.es/ecma262/#sec-math.sinh
864 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh
865 pub(crate) fn sinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
866 Ok(args
867 .get_or_undefined(0)
868 // 1. Let n be ? ToNumber(x).
869 .to_number(context)?
870 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
871 // 3. Return an implementation-approximated value representing the result of the hyperbolic sine of β(n).
872 .sinh()
873 .into())
874 }
875
876 /// Get the square root of a number.
877 ///
878 /// More information:
879 /// - [ECMAScript reference][spec]
880 /// - [MDN documentation][mdn]
881 ///
882 /// [spec]: https://tc39.es/ecma262/#sec-math.sqrt
883 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
884 pub(crate) fn sqrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
885 Ok(args
886 .get_or_undefined(0)
887 // 1. Let n be ? ToNumber(x).
888 .to_number(context)?
889 // 2. If n is NaN, n is +0π½, n is -0π½, or n is +βπ½, return n.
890 // 3. If n < +0π½, return NaN.
891 // 4. Return an implementation-approximated value representing the result of the square root of β(n).
892 .sqrt()
893 .into())
894 }
895
896 /// Get the tangent of a number.
897 ///
898 /// More information:
899 /// - [ECMAScript reference][spec]
900 /// - [MDN documentation][mdn]
901 ///
902 /// [spec]: https://tc39.es/ecma262/#sec-math.tan
903 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
904 pub(crate) fn tan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
905 Ok(args
906 .get_or_undefined(0)
907 // 1. Let n be ? ToNumber(x).
908 .to_number(context)?
909 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
910 // 3. If n is +βπ½, or n is -βπ½, return NaN.
911 // 4. Return an implementation-approximated value representing the result of the tangent of β(n).
912 .tan()
913 .into())
914 }
915
916 /// Get the hyperbolic tangent of a number.
917 ///
918 /// More information:
919 /// - [ECMAScript reference][spec]
920 /// - [MDN documentation][mdn]
921 ///
922 /// [spec]: https://tc39.es/ecma262/#sec-math.tanh
923 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh
924 pub(crate) fn tanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
925 Ok(args
926 .get_or_undefined(0)
927 // 1. Let n be ? ToNumber(x).
928 .to_number(context)?
929 // 2. If n is NaN, n is +0π½, or n is -0π½, return n.
930 // 3. If n is +βπ½, return 1π½.
931 // 4. If n is -βπ½, return -1π½.
932 // 5. Return an implementation-approximated value representing the result of the hyperbolic tangent of β(n).
933 .tanh()
934 .into())
935 }
936
937 /// Get the integer part of a number.
938 ///
939 /// More information:
940 /// - [ECMAScript reference][spec]
941 /// - [MDN documentation][mdn]
942 ///
943 /// [spec]: https://tc39.es/ecma262/#sec-math.trunc
944 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
945 pub(crate) fn trunc(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
946 Ok(args
947 .get_or_undefined(0)
948 // 1. Let n be ? ToNumber(x).
949 .to_number(context)?
950 // 2. If n is NaN, n is +0π½, n is -0π½, n is +βπ½, or n is -βπ½, return n.
951 // 3. If n < 1π½ and n > +0π½, return +0π½.
952 // 4. If n < +0π½ and n > -1π½, return -0π½.
953 // 5. Return the integral Number nearest n in the direction of +0π½.
954 .trunc()
955 .into())
956 }
957
958 /// Sum each value in an iterable
959 ///
960 /// More information:
961 /// - [ECMAScript reference][spec]
962 /// - [MDN documentation][mdn]
963 ///
964 /// [spec]: https://tc39.es/ecma262/#sec-math.sumprecise
965 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sumPrecise
966 #[cfg(feature = "xsum")]
967 pub(crate) fn sum_precise(
968 _: &JsValue,
969 args: &[JsValue],
970 context: &mut Context,
971 ) -> JsResult<JsValue> {
972 const ITERATION_MAX: u64 = 2u64.pow(53) - 1;
973 use xsum::{Xsum, XsumLarge, XsumSmall, XsumVariant, constants::XSUM_THRESHOLD};
974
975 use crate::{builtins::iterable::IteratorHint, js_error};
976
977 let items = args.get_or_undefined(0);
978 // NOTE (nekevss): inline `RequireObjectCoercible`
979 // 1. Perform ? RequireObjectCoercible(items).
980 // RequireObjectCoercible, 1. If argument is either undefined or null, throw a TypeError exception.
981 // RequireObjectCoercible, 2. Return argument.
982 if items.is_null_or_undefined() {
983 return Err(js_error!(TypeError: "value must be object coercible."));
984 }
985
986 // 2. Let iteratorRecord be ? GetIterator(items, sync).
987 let mut iterator_record = items.get_iterator(IteratorHint::Sync, context)?;
988 // 3. Let state be minus-zero.
989 // 4. Let sum be 0.
990 let length = if iterator_record.iterator().is_array() {
991 iterator_record
992 .iterator()
993 .length_of_array_like(context)
994 .ok()
995 } else {
996 None
997 };
998 let mut sum = if let Some(length) = length {
999 if length <= XSUM_THRESHOLD as u64 {
1000 XsumVariant::Small(XsumSmall::new())
1001 } else {
1002 XsumVariant::Large(XsumLarge::new())
1003 }
1004 } else {
1005 XsumVariant::Small(XsumSmall::new())
1006 };
1007 // 5. Let count be 0.
1008 let mut count = 0;
1009 // 6. Let next be not-started.
1010 // 7. Repeat, while next is not done,
1011 // a. Set next to ? IteratorStepValue(iteratorRecord).
1012 while let Some(next) = iterator_record.step_value(context)? {
1013 // b. If next is not done, then
1014 // i. If count β₯ 2**53 - 1, then
1015 if count >= ITERATION_MAX {
1016 // 1. NOTE: This step is not expected to be reached in practice and is
1017 // included only so that implementations may rely on inputs being
1018 // "reasonably sized" without violating this specification.
1019 // 2. Let error be ThrowCompletion(a newly created RangeError object).
1020 let err = Err(js_error!(RangeError: "Summation exceeded maximum iterations"));
1021 // 3. Return ? IteratorClose(iteratorRecord, error).
1022 return iterator_record.close(err, context);
1023 }
1024 // ii. If next is not a Number, then
1025 let Some(number) = next.as_number() else {
1026 // 1. Let error be ThrowCompletion(a newly created TypeError object).
1027 let err = Err(js_error!(TypeError: "sumPrecise can only be called on a number."));
1028 // 2. Return ? IteratorClose(iteratorRecord, error).
1029 return iterator_record.close(err, context);
1030 };
1031 // iii. Let n be next.
1032 // iv. If state is not not-a-number, then
1033 // 1. If n is NaN, then
1034 // a. Set state to not-a-number.
1035 // 2. Else if n is +βπ½, then
1036 // a. If state is minus-infinity, set state to not-a-number.
1037 // b. Else, set state to plus-infinity.
1038 // 3. Else if n is -βπ½, then
1039 // a. If state is plus-infinity, set state to not-a-number.
1040 // b. Else, set state to minus-infinity.
1041 // 4. Else if n is not -0π½ and state is either minus-zero or finite, then
1042 // a. Set state to finite.
1043 // b. Set sum to sum + β(n).
1044 sum.add(number);
1045 // v. Set count to count + 1.
1046 count += 1;
1047 }
1048 // 8. If state is not-a-number, return NaN.
1049 // 9. If state is plus-infinity, return +βπ½.
1050 // 10. If state is minus-infinity, return -βπ½.
1051 // 11. If state is minus-zero, return -0π½.
1052 // 12. Return π½(sum).
1053 Ok(sum.sum().into())
1054 }
1055}