From 5036f575f71bc09f4aca8fb5b5a98b29908b0fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Sun, 3 Oct 2021 19:53:24 +0200 Subject: [PATCH 1/6] Made toExponential and toFixed compliant --- boa/src/builtins/number/mod.rs | 56 ++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 47135798527..a32deaf962f 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -214,6 +214,21 @@ impl Number { } } + /// 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 `num_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 num_to_exponential_with_precision(n: f64, prec: usize) -> String { + let mut res = format!("{:.*e}", prec, n); + let idx = res + .find('e') + .unwrap_or_else(|| unreachable!("'e' not found in exponential string")); + if res.as_bytes()[idx + 1] != b'-' { + res.insert(idx + 1, '+'); + } + res + } + /// `Number.prototype.toExponential( [fractionDigits] )` /// /// The `toExponential()` method returns a string representing the Number object in exponential notation. @@ -227,11 +242,30 @@ impl Number { #[allow(clippy::wrong_self_convention)] pub(crate) fn to_exponential( this: &JsValue, - _: &[JsValue], + args: &[JsValue], context: &mut Context, ) -> JsResult { 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) { + Some(arg) if arg.is_undefined() => None, + Some(n) => Some(n.to_integer(context)? as i32), + None => None, + }; + 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 { + if precision < 0 || precision > 100 { + return Err(context.construct_range_error( + "toExponential() argument must be between 0 and 100", + )); + } + Self::num_to_exponential_with_precision(this_num, precision as usize) + } else { + Self::num_to_exponential(this_num) + }; Ok(JsValue::new(this_str_num)) } @@ -254,13 +288,23 @@ impl Number { let this_num = Self::this_number_value(this, context)?; let precision = match args.get(0) { 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, + _ => { + return Err(context.construct_range_error( + "toFixed() digits argument must be between 0 and 100", + )) + } }, None => 0, }; - let this_fixed_num = format!("{:.*}", precision, this_num); - Ok(JsValue::new(this_fixed_num)) + // Get rid of the '-' sign for -0.0 + let this_num = if this_num == 0. { 0. } else { this_num }; + if this_num >= 1.0e21 { + Ok(JsValue::new(Self::num_to_exponential(this_num))) + } else { + let this_fixed_num = format!("{:.*}", precision, this_num); + Ok(JsValue::new(this_fixed_num)) + } } /// `Number.prototype.toLocaleString( [locales [, options]] )` From a8974221d588e94f7b48f25581d9610ad7893ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Sun, 3 Oct 2021 20:04:28 +0200 Subject: [PATCH 2/6] Formatting --- boa/src/builtins/number/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index a32deaf962f..bbf4551106a 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -257,10 +257,9 @@ impl Number { // 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 { - if precision < 0 || precision > 100 { - return Err(context.construct_range_error( - "toExponential() argument must be between 0 and 100", - )); + if !(0..=100).contains(&precision) { + return Err(context + .construct_range_error("toExponential() argument must be between 0 and 100")); } Self::num_to_exponential_with_precision(this_num, precision as usize) } else { From 8ca5e175a2880c3a30764ca542f5899ff166c8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Mon, 4 Oct 2021 18:12:54 +0200 Subject: [PATCH 3/6] Spec comments and small changes --- boa/src/builtins/number/mod.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index bbf4551106a..bf407a5c274 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -220,9 +220,7 @@ impl Number { // Instead we get the index of 'e', and if the next character is not '-' we insert the plus sign fn num_to_exponential_with_precision(n: f64, prec: usize) -> String { let mut res = format!("{:.*e}", prec, n); - let idx = res - .find('e') - .unwrap_or_else(|| unreachable!("'e' not found in exponential string")); + let idx = res.find('e').expect("'e' not found in exponential string"); if res.as_bytes()[idx + 1] != b'-' { res.insert(idx + 1, '+'); } @@ -245,18 +243,21 @@ impl Number { args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. Let x be ? thisNumberValue(this value). let this_num = Self::this_number_value(this, context)?; let precision = match args.get(0) { - Some(arg) if arg.is_undefined() => None, + None | Some(JsValue::Undefined) => None, + // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). Some(n) => Some(n.to_integer(context)? as i32), - None => None, }; + // 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")); @@ -284,23 +285,31 @@ 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 { 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, }; - // Get rid of the '-' sign for -0.0 - let this_num = if this_num == 0. { 0. } else { this_num }; - if this_num >= 1.0e21 { + // 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(Self::num_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)) } From 4e2e02aea5f2eecea34fca2fa4febe63ff24ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Tue, 5 Oct 2021 21:52:08 +0200 Subject: [PATCH 4/6] Extracting utility functions --- boa/src/builtins/number/mod.rs | 49 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index bf407a5c274..04d24a24450 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -205,28 +205,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), - } - } - - /// 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 `num_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 num_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 - } - /// `Number.prototype.toExponential( [fractionDigits] )` /// /// The `toExponential()` method returns a string representing the Number object in exponential notation. @@ -262,9 +240,9 @@ impl Number { return Err(context .construct_range_error("toExponential() argument must be between 0 and 100")); } - Self::num_to_exponential_with_precision(this_num, precision as usize) + f64_to_exponential_with_precision(this_num, precision as usize) } else { - Self::num_to_exponential(this_num) + f64_to_exponential(this_num) }; Ok(JsValue::new(this_str_num)) } @@ -306,7 +284,7 @@ impl Number { 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(Self::num_to_exponential(this_num))) + 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 }; @@ -1176,3 +1154,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 `num_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 +} From 8b24164458aba28b980c06a42e67e410aaec8f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Tue, 5 Oct 2021 22:05:37 +0200 Subject: [PATCH 5/6] Handle formatting 1.0 --- boa/src/builtins/number/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 04d24a24450..d623df91fdb 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -1158,7 +1158,7 @@ impl Number { /// 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+"), + x if x >= 1.0 || x == 0.0 => format!("{:e}", n).replace("e", "e+"), _ => format!("{:e}", n), } } From f9bd3b117a0bd9b40372a90f0c397054454a01c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikodem=20Rabuli=C5=84ski?= Date: Tue, 5 Oct 2021 22:06:44 +0200 Subject: [PATCH 6/6] Typo --- boa/src/builtins/number/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d623df91fdb..391751cc90f 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -1164,7 +1164,7 @@ fn f64_to_exponential(n: f64) -> String { } /// 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 `num_to_exponential` +// 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 {