Skip to main content

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