Browse Source

Fix `Number.toExponential` and `Number.toFixed` (#1620)

pull/1636/head
Nikodem Rabuliński 3 years ago committed by GitHub
parent
commit
4e2d195b63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 81
      boa/src/builtins/number/mod.rs

81
boa/src/builtins/number/mod.rs

@ -198,15 +198,6 @@ impl Number {
Err(context.construct_type_error("'this' is not a 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] )` /// `Number.prototype.toExponential( [fractionDigits] )`
/// ///
/// The `toExponential()` method returns a string representing the Number object in exponential notation. /// The `toExponential()` method returns a string representing the Number object in exponential notation.
@ -220,11 +211,32 @@ impl Number {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_exponential( pub(crate) fn to_exponential(
this: &JsValue, this: &JsValue,
_: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let x be ? thisNumberValue(this value).
let this_num = Self::this_number_value(this, context)?; 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)) Ok(JsValue::new(this_str_num))
} }
@ -244,16 +256,34 @@ impl Number {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let this_num be ? thisNumberValue(this value).
let this_num = Self::this_number_value(this, context)?; let this_num = Self::this_number_value(this, context)?;
let precision = match args.get(0) { let precision = match args.get(0) {
// 2. Let f be ? ToIntegerOrInfinity(fractionDigits).
Some(n) => match n.to_integer(context)? as i32 { Some(n) => match n.to_integer(context)? as i32 {
x if x > 0 => n.to_integer(context)? as usize, 0..=100 => n.to_integer(context)? as usize,
_ => 0, // 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, None => 0,
}; };
let this_fixed_num = format!("{:.*}", precision, this_num); // 6. If x is not finite, return ! Number::toString(x).
Ok(JsValue::new(this_fixed_num)) 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]] )` /// `Number.prototype.toLocaleString( [locales [, options]] )`
@ -1116,3 +1146,24 @@ impl Number {
!x !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
}

Loading…
Cancel
Save