From dd84439ea02782bc6047716a4e9a620f4fbc6ea3 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 21 Sep 2021 08:01:35 -0500 Subject: [PATCH] Refactor the `Math` builtin object (#1584) * Document the `Math` builtin object * Fix typos and rename variables --- boa/src/builtins/math/mod.rs | 492 +++++++++++++++++++++++------------ 1 file changed, 330 insertions(+), 162 deletions(-) diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 5ebc8c2771..12cdc0683b 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -16,6 +16,8 @@ use crate::{ BoaProfiler, Context, JsResult, JsValue, }; +use super::JsArgs; + #[cfg(test)] mod tests; @@ -37,13 +39,13 @@ impl BuiltIn for Math { let string_tag = WellKnownSymbols::to_string_tag(); let object = ObjectInitializer::new(context) .property("E", std::f64::consts::E, attribute) - .property("LN2", std::f64::consts::LN_2, attribute) .property("LN10", std::f64::consts::LN_10, attribute) - .property("LOG2E", std::f64::consts::LOG2_E, attribute) + .property("LN2", std::f64::consts::LN_2, attribute) .property("LOG10E", std::f64::consts::LOG10_E, attribute) + .property("LOG2E", std::f64::consts::LOG2_E, attribute) + .property("PI", std::f64::consts::PI, attribute) .property("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2, attribute) .property("SQRT2", std::f64::consts::SQRT_2, attribute) - .property("PI", std::f64::consts::PI, attribute) .function(Self::abs, "abs", 1) .function(Self::acos, "acos", 1) .function(Self::acosh, "acosh", 1) @@ -101,10 +103,15 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs pub(crate) fn abs(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::abs) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 3. If n is -0𝔽, return +0𝔽. + // 2. If n is NaN, return NaN. + // 4. If n is -∞𝔽, return +∞𝔽. + // 5. If n < +0𝔽, return -n. + // 6. Return n. + .abs() .into()) } @@ -118,10 +125,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos pub(crate) fn acos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::acos) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n > 1𝔽, or n < -1𝔽, return NaN. + // 3. If n is 1𝔽, return +0𝔽. + // 4. Return an implementation-approximated value representing the result of the inverse cosine of ℝ(n). + .acos() .into()) } @@ -135,10 +145,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh pub(crate) fn acosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::acosh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 4. If n < 1𝔽, return NaN. + // 2. If n is NaN or n is +∞𝔽, return n. + // 3. If n is 1𝔽, return +0𝔽. + // 5. Return an implementation-approximated value representing the result of the inverse hyperbolic cosine of ℝ(n). + .acosh() .into()) } @@ -152,10 +166,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin pub(crate) fn asin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::asin) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n > 1𝔽 or n < -1𝔽, return NaN. + // 4. Return an implementation-approximated value representing the result of the inverse sine of ℝ(n). + .asin() .into()) } @@ -169,10 +186,12 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh pub(crate) fn asinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::asinh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. Return an implementation-approximated value representing the result of the inverse hyperbolic sine of ℝ(n). + .asinh() .into()) } @@ -186,10 +205,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan pub(crate) fn atan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::atan) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n is +∞𝔽, return an implementation-approximated value representing π / 2. + // 4. If n is -∞𝔽, return an implementation-approximated value representing -π / 2. + // 5. Return an implementation-approximated value representing the result of the inverse tangent of ℝ(n). + .atan() .into()) } @@ -203,14 +226,19 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh pub(crate) fn atanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::atanh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n > 1𝔽 or n < -1𝔽, return NaN. + // 4. If n is 1𝔽, return +∞𝔽. + // 5. If n is -1𝔽, return -∞𝔽. + // 6. Return an implementation-approximated value representing the result of the inverse hyperbolic tangent of ℝ(n). + .atanh() .into()) } - /// Get the arctangent of a numbers. + /// Get the four quadrant arctangent of the quotient y / x. /// /// More information: /// - [ECMAScript reference][spec] @@ -219,14 +247,38 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.atan2 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 pub(crate) fn atan2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - Ok(match ( - args.get(0).map(|x| x.to_number(context)).transpose()?, - args.get(1).map(|x| x.to_number(context)).transpose()?, - ) { - (Some(x), Some(y)) => x.atan2(y), - (_, _) => f64::NAN, - } - .into()) + // 1. Let ny be ? ToNumber(y). + let y = args.get_or_undefined(0).to_number(context)?; + + // 2. Let nx be ? ToNumber(x). + let x = args.get_or_undefined(1).to_number(context)?; + + // 4. If ny is +∞𝔽, then + // a. If nx is +∞𝔽, return an implementation-approximated value representing π / 4. + // b. If nx is -∞𝔽, return an implementation-approximated value representing 3π / 4. + // c. Return an implementation-approximated value representing π / 2. + // 5. If ny is -∞𝔽, then + // a. If nx is +∞𝔽, return an implementation-approximated value representing -π / 4. + // b. If nx is -∞𝔽, return an implementation-approximated value representing -3π / 4. + // c. Return an implementation-approximated value representing -π / 2. + // 6. If ny is +0𝔽, then + // a. If nx > +0𝔽 or nx is +0𝔽, return +0𝔽. + // b. Return an implementation-approximated value representing π. + // 7. If ny is -0𝔽, then + // a. If nx > +0𝔽 or nx is +0𝔽, return -0𝔽. + // b. Return an implementation-approximated value representing -π. + // 8. Assert: ny is finite and is neither +0𝔽 nor -0𝔽. + // 9. If ny > +0𝔽, then + // a. If nx is +∞𝔽, return +0𝔽. + // b. If nx is -∞𝔽, return an implementation-approximated value representing π. + // c. If nx is +0𝔽 or nx is -0𝔽, return an implementation-approximated value representing π / 2. + // 10. If ny < +0𝔽, then + // a. If nx is +∞𝔽, return -0𝔽. + // b. If nx is -∞𝔽, return an implementation-approximated value representing -π. + // c. If nx is +0𝔽 or nx is -0𝔽, return an implementation-approximated value representing -π / 2. + // 11. Assert: nx is finite and is neither +0𝔽 nor -0𝔽. + // 12. Return an implementation-approximated value representing the result of the inverse tangent of the quotient ℝ(ny) / ℝ(nx). + Ok(y.atan2(x).into()) } /// Get the cubic root of a number. @@ -239,10 +291,12 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt pub(crate) fn cbrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::cbrt) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. Return an implementation-approximated value representing the result of the cube root of ℝ(n). + .cbrt() .into()) } @@ -256,10 +310,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil pub(crate) fn ceil(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::ceil) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. If n < +0𝔽 and n > -1𝔽, return -0𝔽. + // 4. If n is an integral Number, return n. + // 5. Return the smallest (closest to -∞) integral Number value that is not less than n. + .ceil() .into()) } @@ -273,11 +331,12 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 pub(crate) fn clz32(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_u32(context)) - .transpose()? - .map(u32::leading_zeros) - .unwrap_or(32) + .get_or_undefined(0) + // 1. Let n be ? ToUint32(x). + .to_u32(context)? + // 2. Let p be the number of leading zero bits in the unsigned 32-bit binary representation of n. + // 3. Return 𝔽(p). + .leading_zeros() .into()) } @@ -291,10 +350,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos pub(crate) fn cos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::cos) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +∞𝔽, or n is -∞𝔽, return NaN. + // 3. If n is +0𝔽 or n is -0𝔽, return 1𝔽. + // 4. Return an implementation-approximated value representing the result of the cosine of ℝ(n). + .cos() .into()) } @@ -308,10 +370,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh pub(crate) fn cosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::cosh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, return NaN. + // 3. If n is +∞𝔽 or n is -∞𝔽, return +∞𝔽. + // 4. If n is +0𝔽 or n is -0𝔽, return 1𝔽. + // 5. Return an implementation-approximated value representing the result of the hyperbolic cosine of ℝ(n). + .cosh() .into()) } @@ -325,10 +391,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp pub(crate) fn exp(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::exp) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN or n is +∞𝔽, return n. + // 3. If n is +0𝔽 or n is -0𝔽, return 1𝔽. + // 4. If n is -∞𝔽, return +0𝔽. + // 5. Return an implementation-approximated value representing the result of the exponential function of ℝ(n). + .exp() .into()) } @@ -344,10 +414,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1 pub(crate) fn expm1(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::exp_m1) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n. + // 3. If n is -∞𝔽, return -1𝔽. + // 4. Return an implementation-approximated value representing the result of subtracting 1 from the exponential function of ℝ(n). + .exp_m1() .into()) } @@ -361,10 +434,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor pub(crate) fn floor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::floor) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. If n < 1𝔽 and n > +0𝔽, return +0𝔽. + // 4. If n is an integral Number, return n. + // 5. Return the greatest (closest to +∞) integral Number value that is not greater than n. + .floor() .into()) } @@ -381,12 +458,15 @@ impl Math { args: &[JsValue], context: &mut Context, ) -> JsResult { - Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, |x| (x as f32) as f64) - .into()) + // 1. Let n be ? ToNumber(x). + let x = args.get_or_undefined(0).to_number(context)?; + + // 2. If n is NaN, return NaN. + // 3. If n is one of +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return n. + // 4. Let n32 be the result of converting n to a value in IEEE 754-2019 binary32 format using roundTiesToEven mode. + // 5. Let n64 be the result of converting n32 to a value in IEEE 754-2019 binary64 format. + // 6. Return the ECMAScript Number value corresponding to n64. + Ok((x as f32 as f64).into()) } /// Get an approximation of the square root of the sum of squares of all arguments. @@ -398,11 +478,24 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.hypot /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot pub(crate) fn hypot(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let coerced be a new empty List. + // 2. For each element arg of args, do + // a. Let n be ? ToNumber(arg). + // b. Append n to coerced. + // 3. For each element number of coerced, do + // 5. For each element number of coerced, do let mut result = 0f64; for arg in args { - let x = arg.to_number(context)?; - result = result.hypot(x); + let num = arg.to_number(context)?; + // a. If number is +∞𝔽 or number is -∞𝔽, return +∞𝔽. + // 4. Let onlyZero be true. + // a. If number is NaN, return NaN. + // b. If number is neither +0𝔽 nor -0𝔽, set onlyZero to false. + // 6. If onlyZero is true, return +0𝔽. + // 7. Return an implementation-approximated value representing the square root of the sum of squares of the mathematical values of the elements of coerced. + result = result.hypot(num); } + Ok(result.into()) } @@ -415,14 +508,15 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.imul /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul pub(crate) fn imul(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - Ok(match ( - args.get(0).map(|x| x.to_u32(context)).transpose()?, - args.get(1).map(|x| x.to_u32(context)).transpose()?, - ) { - (Some(x), Some(y)) => x.wrapping_mul(y) as i32, - (_, _) => 0, - } - .into()) + // 1. Let a be ℝ(? ToUint32(x)). + let x = args.get_or_undefined(0).to_u32(context)?; + + // 2. Let b be ℝ(? ToUint32(y)). + let y = args.get_or_undefined(1).to_u32(context)?; + + // 3. Let product be (a × b) modulo 2^32. + // 4. If product ≥ 2^31, return 𝔽(product - 2^32); otherwise return 𝔽(product). + Ok((x.wrapping_mul(y) as i32).into()) } /// Get the natural logarithm of a number. @@ -435,10 +529,15 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log pub(crate) fn log(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.ln() }) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN or n is +∞𝔽, return n. + // 3. If n is 1𝔽, return +0𝔽. + // 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽. + // 5. If n < +0𝔽, return NaN. + // 6. Return an implementation-approximated value representing the result of the natural logarithm of ℝ(n). + .ln() .into()) } @@ -452,10 +551,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p pub(crate) fn log1p(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::ln_1p) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n. + // 3. If n is -1𝔽, return -∞𝔽. + // 4. If n < -1𝔽, return NaN. + // 5. Return an implementation-approximated value representing the result of the natural logarithm of 1 + ℝ(n). + .ln_1p() .into()) } @@ -469,10 +572,15 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 pub(crate) fn log10(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log10() }) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN or n is +∞𝔽, return n. + // 3. If n is 1𝔽, return +0𝔽. + // 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽. + // 5. If n < +0𝔽, return NaN. + // 6. Return an implementation-approximated value representing the result of the base 10 logarithm of ℝ(n). + .log10() .into()) } @@ -486,10 +594,15 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 pub(crate) fn log2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log2() }) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN or n is +∞𝔽, return n. + // 3. If n is 1𝔽, return +0𝔽. + // 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽. + // 5. If n < +0𝔽, return NaN. + // 6. Return an implementation-approximated value representing the result of the base 2 logarithm of ℝ(n). + .log2() .into()) } @@ -502,12 +615,30 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.max /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max pub(crate) fn max(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let mut max = f64::NEG_INFINITY; + // 1. Let coerced be a new empty List. + // 2. For each element arg of args, do + // b. Append n to coerced. + // 3. Let highest be -∞𝔽. + let mut highest = f64::NEG_INFINITY; + + // 4. For each element number of coerced, do for arg in args { + // a. Let n be ? ToNumber(arg). let num = arg.to_number(context)?; - max = max.max(num); + + highest = if highest.is_nan() { + continue; + } else if num.is_nan() { + // a. If number is NaN, return NaN. + f64::NAN + } else { + // b. If number is +0𝔽 and highest is -0𝔽, set highest to +0𝔽. + // c. If number > highest, set highest to number. + highest.max(num) + }; } - Ok(max.into()) + // 5. Return highest. + Ok(highest.into()) } /// Get the minimum of several numbers. @@ -519,12 +650,30 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.min /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min pub(crate) fn min(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let mut min = f64::INFINITY; + // 1. Let coerced be a new empty List. + // 2. For each element arg of args, do + // b. Append n to coerced. + // 3. Let lowest be +∞𝔽. + let mut lowest = f64::INFINITY; + + // 4. For each element number of coerced, do for arg in args { + // a. Let n be ? ToNumber(arg). let num = arg.to_number(context)?; - min = min.min(num); + + lowest = if lowest.is_nan() { + continue; + } else if num.is_nan() { + // a. If number is NaN, return NaN. + f64::NAN + } else { + // b. If number is -0𝔽 and lowest is +0𝔽, set lowest to -0𝔽. + // c. If number < lowest, set lowest to number. + lowest.min(num) + }; } - Ok(min.into()) + // 5. Return lowest. + Ok(lowest.into()) } /// Raise a number to a power. @@ -536,14 +685,14 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.pow /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow pub(crate) fn pow(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - Ok(match ( - args.get(0).map(|x| x.to_number(context)).transpose()?, - args.get(1).map(|x| x.to_number(context)).transpose()?, - ) { - (Some(x), Some(y)) => x.powf(y), - (_, _) => f64::NAN, - } - .into()) + // 1. Set base to ? ToNumber(base). + let x = args.get_or_undefined(0).to_number(context)?; + + // 2. Set exponent to ? ToNumber(exponent). + let y = args.get_or_undefined(1).to_number(context)?; + + // 3. Return ! Number::exponentiate(base, exponent). + Ok(x.powf(y).into()) } /// Generate a random floating-point number between `0` and `1`. @@ -555,6 +704,7 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.random /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random pub(crate) fn random(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // NOTE: Each Math.random function created for distinct realms must produce a distinct sequence of values from successive calls. Ok(rand::random::().into()) } @@ -568,10 +718,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round pub(crate) fn round(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::round) + .get_or_undefined(0) + //1. Let n be ? ToNumber(x). + .to_number(context)? + //2. If n is NaN, +∞𝔽, -∞𝔽, or an integral Number, return n. + //3. If n < 0.5𝔽 and n > +0𝔽, return +0𝔽. + //4. If n < +0𝔽 and n ≥ -0.5𝔽, return -0𝔽. + //5. Return the integral Number closest to n, preferring the Number closer to +∞ in the case of a tie. + .round() .into()) } @@ -584,21 +738,16 @@ impl Math { /// [spec]: https://tc39.es/ecma262/#sec-math.sign /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign pub(crate) fn sign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or( - f64::NAN, - |x| { - if x == 0.0 || x == -0.0 { - x - } else { - x.signum() - } - }, - ) - .into()) + // 1. Let n be ? ToNumber(x). + let n = args.get_or_undefined(0).to_number(context)?; + + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + if n == 0.0 { + return Ok(n.into()); + } + // 3. If n < +0𝔽, return -1𝔽. + // 4. Return 1𝔽. + Ok(n.signum().into()) } /// Get the sine of a number. @@ -611,10 +760,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin pub(crate) fn sin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::sin) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n is +∞𝔽 or n is -∞𝔽, return NaN. + // 4. Return an implementation-approximated value representing the result of the sine of ℝ(n). + .sin() .into()) } @@ -628,10 +780,12 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh pub(crate) fn sinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::sinh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. Return an implementation-approximated value representing the result of the hyperbolic sine of ℝ(n). + .sinh() .into()) } @@ -645,10 +799,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt pub(crate) fn sqrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::sqrt) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n. + // 3. If n < +0𝔽, return NaN. + // 4. Return an implementation-approximated value representing the result of the square root of ℝ(n). + .sqrt() .into()) } @@ -662,10 +819,13 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan pub(crate) fn tan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::tan) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n is +∞𝔽, or n is -∞𝔽, return NaN. + // 4. Return an implementation-approximated value representing the result of the tangent of ℝ(n). + .tan() .into()) } @@ -679,10 +839,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh pub(crate) fn tanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::tanh) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n. + // 3. If n is +∞𝔽, return 1𝔽. + // 4. If n is -∞𝔽, return -1𝔽. + // 5. Return an implementation-approximated value representing the result of the hyperbolic tangent of ℝ(n). + .tanh() .into()) } @@ -696,10 +860,14 @@ impl Math { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc pub(crate) fn trunc(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { Ok(args - .get(0) - .map(|x| x.to_number(context)) - .transpose()? - .map_or(f64::NAN, f64::trunc) + .get_or_undefined(0) + // 1. Let n be ? ToNumber(x). + .to_number(context)? + // 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n. + // 3. If n < 1𝔽 and n > +0𝔽, return +0𝔽. + // 4. If n < +0𝔽 and n > -1𝔽, return -0𝔽. + // 5. Return the integral Number nearest n in the direction of +0𝔽. + .trunc() .into()) } }