diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 2cf2b5b8f8..a7a49b8872 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -198,15 +198,6 @@ impl Number { Err(context.construct_type_error("'this' is not a number")) } - /// Helper function that formats a float as a ES6-style exponential number string. - fn num_to_exponential(n: f64) -> String { - match n.abs() { - x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), - x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), - _ => format!("{:e}", n), - } - } - /// `Number.prototype.toExponential( [fractionDigits] )` /// /// The `toExponential()` method returns a string representing the Number object in exponential notation. @@ -220,11 +211,32 @@ impl Number { #[allow(clippy::wrong_self_convention)] pub(crate) fn to_exponential( this: &JsValue, - _: &[JsValue], + args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. Let x be ? thisNumberValue(this value). let this_num = Self::this_number_value(this, context)?; - let this_str_num = Self::num_to_exponential(this_num); + let precision = match args.get(0) { + None | Some(JsValue::Undefined) => None, + // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). + Some(n) => Some(n.to_integer(context)? as i32), + }; + // 4. If x is not finite, return ! Number::toString(x). + if !this_num.is_finite() { + return Ok(JsValue::new(Self::to_native_string(this_num))); + } + // Get rid of the '-' sign for -0.0 + let this_num = if this_num == 0. { 0. } else { this_num }; + let this_str_num = if let Some(precision) = precision { + // 5. If f < 0 or f > 100, throw a RangeError exception. + if !(0..=100).contains(&precision) { + return Err(context + .construct_range_error("toExponential() argument must be between 0 and 100")); + } + f64_to_exponential_with_precision(this_num, precision as usize) + } else { + f64_to_exponential(this_num) + }; Ok(JsValue::new(this_str_num)) } @@ -244,16 +256,34 @@ impl Number { args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. Let this_num be ? thisNumberValue(this value). let this_num = Self::this_number_value(this, context)?; let precision = match args.get(0) { + // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). Some(n) => match n.to_integer(context)? as i32 { - x if x > 0 => n.to_integer(context)? as usize, - _ => 0, + 0..=100 => n.to_integer(context)? as usize, + // 4, 5. If f < 0 or f > 100, throw a RangeError exception. + _ => { + return Err(context.construct_range_error( + "toFixed() digits argument must be between 0 and 100", + )) + } }, + // 3. If fractionDigits is undefined, then f is 0. None => 0, }; - let this_fixed_num = format!("{:.*}", precision, this_num); - Ok(JsValue::new(this_fixed_num)) + // 6. If x is not finite, return ! Number::toString(x). + if !this_num.is_finite() { + Ok(JsValue::new(Self::to_native_string(this_num))) + // 10. If x ≥ 10^21, then let m be ! ToString(𝔽(x)). + } else if this_num >= 1.0e21 { + Ok(JsValue::new(f64_to_exponential(this_num))) + } else { + // Get rid of the '-' sign for -0.0 because of 9. If x < 0, then set s to "-". + let this_num = if this_num == 0. { 0. } else { this_num }; + let this_fixed_num = format!("{:.*}", precision, this_num); + Ok(JsValue::new(this_fixed_num)) + } } /// `Number.prototype.toLocaleString( [locales [, options]] )` @@ -1116,3 +1146,24 @@ impl Number { !x } } + +/// Helper function that formats a float as a ES6-style exponential number string. +fn f64_to_exponential(n: f64) -> String { + match n.abs() { + x if x >= 1.0 || x == 0.0 => format!("{:e}", n).replace("e", "e+"), + _ => format!("{:e}", n), + } +} + +/// Helper function that formats a float as a ES6-style exponential number string with a given precision. +// We can't use the same approach as in `f64_to_exponential` +// because in cases like (0.999).toExponential(0) the result will be 1e0. +// Instead we get the index of 'e', and if the next character is not '-' we insert the plus sign +fn f64_to_exponential_with_precision(n: f64, prec: usize) -> String { + let mut res = format!("{:.*e}", prec, n); + let idx = res.find('e').expect("'e' not found in exponential string"); + if res.as_bytes()[idx + 1] != b'-' { + res.insert(idx + 1, '+'); + } + res +}