diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index e1753997bcd..d973baba4ba 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -318,28 +318,57 @@ impl Number { /// represented by these digits is rounded using string /// manipulation. /// - Else, zeroes are appended to the string. + /// - Additionnally, sometimes the exponent was wrongly computed and + /// while up-rounding we find that we need an extra digit. When this + /// happens, we return true so that the calling context can adjust + /// the exponent. The string is kept at an exact length of `precision`. /// /// When this procedure returns, `digits` is exactly `precision` long. /// - fn round_to_precision(digits: &mut String, precision: usize) { + fn round_to_precision(digits: &mut String, precision: usize) -> bool { if digits.len() > precision { let to_round = digits.split_off(precision); let mut digit = digits.pop().unwrap() as u8; + if let Some(first) = to_round.chars().next() { + if first > '4' { + digit += 1; + } + } - for c in to_round.chars() { - match c { - c if c < '4' => break, - c if c > '4' => { - digit += 1; - break; + if digit as char == ':' { + // ':' is '9' + 1 + // need to propagate the increment backward + let mut replacement = String::from("0"); + let mut propagated = false; + for c in digits.chars().rev() { + let d = match (c, propagated) { + ('0'..='8', false) => (c as u8 + 1) as char, + (_, false) => '0', + (_, true) => c, + }; + replacement.push(d); + if d != '0' { + propagated = true; } - _ => {} } + digits.clear(); + let replacement = if !propagated { + digits.push('1'); + &replacement.as_str()[1..] + } else { + replacement.as_str() + }; + for c in replacement.chars().rev() { + digits.push(c) + } + !propagated + } else { + digits.push(digit as char); + false } - - digits.push(digit as char); } else { digits.push_str(&"0".repeat(precision - digits.len())); + false } } @@ -359,17 +388,24 @@ impl Number { args: &[Value], context: &mut Context, ) -> Result { - let precision_var = args.get(0).cloned().unwrap_or_default(); + let precision = args.get(0).cloned().unwrap_or_default(); // 1 & 6 let mut this_num = Self::this_number_value(this, context)?; - // 2 & 4 - if precision_var == Value::undefined() || !this_num.is_finite() { + // 2 + if precision == Value::undefined() { return Self::to_string(this, &[], context); } // 3 - let precision = match precision_var.to_integer_or_infinity(context)? { + let precision = precision.to_integer_or_infinity(context)?; + + // 4 + if !this_num.is_finite() { + return Self::to_string(this, &[], context); + } + + let precision = match precision { IntegerOrInfinity::Integer(x) if (1..=100).contains(&x) => x as usize, _ => { // 5 @@ -383,7 +419,7 @@ impl Number { // 7 let mut prefix = String::new(); // spec: 's' let mut suffix: String; // spec: 'm' - let exponent: i32; // spec: 'e' + let mut exponent: i32; // spec: 'e' // 8 if this_num < 0.0 { @@ -399,10 +435,8 @@ impl Number { } else { // Due to f64 limitations, this part differs a bit from the spec, // but has the same effect. It manipulates the string constructed - // by ryu-js: digits with an optional dot between two of them. - - let mut buffer = ryu_js::Buffer::new(); - suffix = buffer.format(this_num).to_string(); + // by `format`: digits with an optional dot between two of them. + suffix = format!("{:.100}", this_num); // a: getting an exponent exponent = Self::flt_str_to_exp(&suffix); @@ -413,7 +447,9 @@ impl Number { suffix.remove(n); } // impl: having exactly `precision` digits in `suffix` - Self::round_to_precision(&mut suffix, precision); + if Self::round_to_precision(&mut suffix, precision) { + exponent += 1; + } // c: switching to scientific notation let great_exp = exponent >= precision_i32; diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index e3f8d0ae50f..634c527963c 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -137,6 +137,8 @@ fn to_precision() { var exact_precision = (123456789).toPrecision(9); var over_precision = (123456789).toPrecision(50); var neg_precision = (-123456789).toPrecision(4); + var neg_exponent = (0.1).toPrecision(4); + var ieee754_limits = (1/3).toPrecision(60); "#; eprintln!("{}", forward(&mut context, init)); @@ -148,6 +150,8 @@ fn to_precision() { let exact_precision = forward(&mut context, "exact_precision"); let over_precision = forward(&mut context, "over_precision"); let neg_precision = forward(&mut context, "neg_precision"); + let neg_exponent = forward(&mut context, "neg_exponent"); + let ieee754_limits = forward(&mut context, "ieee754_limits"); assert_eq!(infinity, String::from("\"Infinity\"")); assert_eq!(default_precision, String::from("\"0\"")); @@ -160,6 +164,11 @@ fn to_precision() { over_precision, String::from("\"123456789.00000000000000000000000000000000000000000\"") ); + assert_eq!(neg_exponent, String::from("\"0.1000\"")); + assert_eq!( + ieee754_limits, + String::from("\"0.333333333333333314829616256247390992939472198486328125000000\"") + ); let expected = "Uncaught \"RangeError\": \"precision must be an integer at least 1 and no greater than 100\""; diff --git a/boa/src/vm/instructions.rs b/boa/src/vm/instructions.rs index 39ea22abbd4..0b40a2334c1 100644 --- a/boa/src/vm/instructions.rs +++ b/boa/src/vm/instructions.rs @@ -12,7 +12,7 @@ pub enum Instruction { /// Loads an i32 onto the stack Int32(i32), - /// Loads an f32 onto the stack + /// Loads an f64 onto the stack Rational(f64), /// Adds the values from destination and source and stores the result in destination