From 3bff4df642f373778fbb164f7d0adf631195e487 Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Sat, 12 Dec 2020 21:34:24 +0100 Subject: [PATCH 1/8] Implement Number.prototype.toPrecision Closes #349. --- boa/src/builtins/number/mod.rs | 115 ++++++++++++++++++++++++++++--- boa/src/builtins/number/tests.rs | 13 ++-- 2 files changed, 111 insertions(+), 17 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index c1c19828d23..339fbf9b6da 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -269,6 +269,45 @@ impl Number { Ok(Value::from(this_str_num)) } + fn flt_str_to_exp(flt: &String) -> i32 { + let mut nz_encountered = false; + let mut dot_encountered = false; + for (i, c) in flt.chars().enumerate() { + if c == '.' { + if nz_encountered { + return (i as i32) - 1; + } + dot_encountered = true; + } else if c != '0' { + if dot_encountered { + return 1 - (i as i32); + } + nz_encountered = true; + } + } + (flt.len() as i32) - 1 + } + + fn round_to_precision(digits: &mut String, precision: usize) { + if digits.len() > precision { + let to_round = digits.split_off(precision); + let mut digit = digits.pop().unwrap() as u8; + + for c in to_round.chars() { + if c < '4' { + break; + } else if c > '4' { + digit += 1; + break; + } + } + + digits.push(digit as char); + } else { + digits.push_str(&"0".repeat(precision - digits.len())); + } + } + /// `Number.prototype.toPrecision( [precision] )` /// /// The `toPrecision()` method returns a string representing the Number object to the specified precision. @@ -285,17 +324,73 @@ impl Number { args: &[Value], context: &mut Context, ) -> Result { - let this_num = Self::this_number_value(this, context)?; - let _num_str_len = format!("{}", this_num).len(); - 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, - }, - None => 0, + let precision_arg = args.get(0); + +/*1,6*/ let mut this_num = Self::this_number_value(this, context)?; +/*2,4*/ if precision_arg.is_none() || !this_num.is_finite() { + return Self::to_string(this, &[], context); + } + + let precision = match precision_arg.unwrap().to_integer(context)? as i32 { + x @ 1..=100 => x as usize, +/*5*/ _ => return context.throw_range_error("precision must be an integer at least 1 and no greater than 100"), }; - // TODO: Implement toPrecision - unimplemented!("TODO: Implement toPrecision"); + let precision_i32 = precision as i32; + +/*7*/ let mut prefix = String::new(); // spec: "s" + let mut suffix: String; // spec: "m" + let exponent: i32; // spec: "e" + +/*8*/ if this_num < 0.0 { +/*a*/ prefix.push('-'); +/*b*/ this_num = -this_num; + } + + let mut is_scientific = false; + +/*9*/ if this_num == 0.0 { +/*a*/ suffix = "0".repeat(precision); +/*b*/ exponent = 0; +/*10*/ } else { + let mut buffer = ryu_js::Buffer::new(); +/*b*/ suffix = buffer.format(this_num).to_string(); + +/*a*/ exponent = Self::flt_str_to_exp(&suffix); + if exponent < 0 { + suffix = suffix.split_off((1 - exponent) as usize); + } else if let Some(n) = suffix.find('.') { + suffix.remove(n); + } + Self::round_to_precision(&mut suffix, precision); + + let great_exp = exponent >= precision_i32; +/*c*/ if exponent < -6 || great_exp { + is_scientific = true; + suffix.truncate(precision); + if precision > 1 { + suffix.insert(1, '.'); + } + suffix.push('e'); + if great_exp { suffix.push('+'); } + suffix.push_str(&exponent.to_string()); + } + } + + let e_inc = exponent + 1; +/*11*/ if !is_scientific && e_inc != precision_i32 { +/*12*/ if exponent >= 0 { +/*a*/ suffix.truncate(precision); + if precision > e_inc as usize { + suffix.insert(e_inc as usize, '.'); + } +/*13*/ } else { +/*a*/ prefix.push('0'); + prefix.push('.'); + prefix.push_str(&"0".repeat(-e_inc as usize)); + } + } + +/*14*/ Ok(Value::from(prefix + &suffix)) } // https://golang.org/src/math/nextafter.go diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index b53ddf5ea01..51a24b1e8c8 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -126,7 +126,6 @@ fn to_locale_string() { } #[test] -#[ignore] fn to_precision() { let mut context = Context::new(); let init = r#" @@ -146,15 +145,15 @@ fn to_precision() { let over_precision = forward(&mut context, "over_precision"); let neg_precision = forward(&mut context, "neg_precision"); - assert_eq!(default_precision, String::from("0")); - assert_eq!(low_precision, String::from("1e+8")); - assert_eq!(more_precision, String::from("1.235e+8")); - assert_eq!(exact_precision, String::from("123456789")); + assert_eq!(default_precision, String::from("\"0\"")); + assert_eq!(low_precision, String::from("\"1e+8\"")); + assert_eq!(more_precision, String::from("\"1.235e+8\"")); + assert_eq!(exact_precision, String::from("\"123456789\"")); + assert_eq!(neg_precision, String::from("\"-1.235e+8\"")); assert_eq!( over_precision, - String::from("123456789.00000000000000000000000000000000000000000") + String::from("\"123456789.00000000000000000000000000000000000000000\"") ); - assert_eq!(neg_precision, String::from("-1.235e+8")); } #[test] From 35ab370feaed88ae68acf1e9343f64c32ece040a Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Sat, 12 Dec 2020 22:04:13 +0100 Subject: [PATCH 2/8] Add rustfmt and clippy conformance to the implementation of Number.prototype.toPrecision --- boa/src/builtins/number/mod.rs | 62 +++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 339fbf9b6da..0d1d2ffcbae 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -269,7 +269,7 @@ impl Number { Ok(Value::from(this_str_num)) } - fn flt_str_to_exp(flt: &String) -> i32 { + fn flt_str_to_exp(flt: &str) -> i32 { let mut nz_encountered = false; let mut dot_encountered = false; for (i, c) in flt.chars().enumerate() { @@ -294,11 +294,13 @@ impl Number { let mut digit = digits.pop().unwrap() as u8; for c in to_round.chars() { - if c < '4' { - break; - } else if c > '4' { - digit += 1; - break; + match c { + c if c < '4' => break, + c if c > '4' => { + digit += 1; + break; + } + _ => {} } } @@ -326,36 +328,40 @@ impl Number { ) -> Result { let precision_arg = args.get(0); -/*1,6*/ let mut this_num = Self::this_number_value(this, context)?; -/*2,4*/ if precision_arg.is_none() || !this_num.is_finite() { + let mut this_num = Self::this_number_value(this, context)?; + if precision_arg.is_none() || !this_num.is_finite() { return Self::to_string(this, &[], context); } let precision = match precision_arg.unwrap().to_integer(context)? as i32 { x @ 1..=100 => x as usize, -/*5*/ _ => return context.throw_range_error("precision must be an integer at least 1 and no greater than 100"), + _ => { + return context.throw_range_error( + "precision must be an integer at least 1 and no greater than 100", + ) + } }; let precision_i32 = precision as i32; -/*7*/ let mut prefix = String::new(); // spec: "s" + let mut prefix = String::new(); // spec: "s" let mut suffix: String; // spec: "m" let exponent: i32; // spec: "e" -/*8*/ if this_num < 0.0 { -/*a*/ prefix.push('-'); -/*b*/ this_num = -this_num; + if this_num < 0.0 { + prefix.push('-'); + this_num = -this_num; } let mut is_scientific = false; -/*9*/ if this_num == 0.0 { -/*a*/ suffix = "0".repeat(precision); -/*b*/ exponent = 0; -/*10*/ } else { + if this_num == 0.0 { + suffix = "0".repeat(precision); + exponent = 0; + } else { let mut buffer = ryu_js::Buffer::new(); -/*b*/ suffix = buffer.format(this_num).to_string(); + suffix = buffer.format(this_num).to_string(); -/*a*/ exponent = Self::flt_str_to_exp(&suffix); + exponent = Self::flt_str_to_exp(&suffix); if exponent < 0 { suffix = suffix.split_off((1 - exponent) as usize); } else if let Some(n) = suffix.find('.') { @@ -364,33 +370,35 @@ impl Number { Self::round_to_precision(&mut suffix, precision); let great_exp = exponent >= precision_i32; -/*c*/ if exponent < -6 || great_exp { + if exponent < -6 || great_exp { is_scientific = true; suffix.truncate(precision); if precision > 1 { suffix.insert(1, '.'); } suffix.push('e'); - if great_exp { suffix.push('+'); } + if great_exp { + suffix.push('+'); + } suffix.push_str(&exponent.to_string()); } } let e_inc = exponent + 1; -/*11*/ if !is_scientific && e_inc != precision_i32 { -/*12*/ if exponent >= 0 { -/*a*/ suffix.truncate(precision); + if !is_scientific && e_inc != precision_i32 { + if exponent >= 0 { + suffix.truncate(precision); if precision > e_inc as usize { suffix.insert(e_inc as usize, '.'); } -/*13*/ } else { -/*a*/ prefix.push('0'); + } else { + prefix.push('0'); prefix.push('.'); prefix.push_str(&"0".repeat(-e_inc as usize)); } } -/*14*/ Ok(Value::from(prefix + &suffix)) + Ok(Value::from(prefix + &suffix)) } // https://golang.org/src/math/nextafter.go From b2654acce93fe3b0cc04010828c8722128822cd6 Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 12:50:06 +0100 Subject: [PATCH 3/8] Corrected links in documentation for Number.prototype.toPrecision --- boa/src/builtins/number/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 0d1d2ffcbae..1bbc8c9395f 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -10,8 +10,8 @@ //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! -//! [spec]: https://tc39.es/ecma262/#sec-number-object -//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number +//! [spec]: https://tc39.es/ecma262/#sec-number.prototype.toprecision +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision use super::function::make_builtin_fn; use crate::{ From afcef3455ae256b6162b857e728e615cbc4b1eb3 Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 14:57:17 +0100 Subject: [PATCH 4/8] Fixed links to spec, code comments, tests and implementation is closer to the spec for Number.prototype.toPrecision --- boa/src/builtins/number/mod.rs | 28 ++++++++++++++++++++-------- boa/src/builtins/number/tests.rs | 28 +++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 1bbc8c9395f..ef0427fdf4e 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -10,15 +10,15 @@ //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! -//! [spec]: https://tc39.es/ecma262/#sec-number.prototype.toprecision -//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision +//! [spec]: https://tc39.es/ecma262/#sec-number-object +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number use super::function::make_builtin_fn; use crate::{ builtins::BuiltIn, object::{ConstructorBuilder, ObjectData}, property::Attribute, - value::{AbstractRelation, Value}, + value::{AbstractRelation, IntegerOrInfinity, Value}, BoaProfiler, Context, Result, }; use num_traits::float::FloatCore; @@ -318,7 +318,7 @@ impl Number { /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// - /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toprecision /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision #[allow(clippy::wrong_self_convention)] pub(crate) fn to_precision( @@ -326,16 +326,20 @@ impl Number { args: &[Value], context: &mut Context, ) -> Result { - let precision_arg = args.get(0); + let precision_var = args.get(0).cloned().unwrap_or_default(); + // 1 & 6 let mut this_num = Self::this_number_value(this, context)?; - if precision_arg.is_none() || !this_num.is_finite() { + // 2 & 4 + if precision_var == Value::undefined() || !this_num.is_finite() { return Self::to_string(this, &[], context); } - let precision = match precision_arg.unwrap().to_integer(context)? as i32 { - x @ 1..=100 => x as usize, + // 3 + let precision = match precision_var.to_integer_or_infinity(context).unwrap() { + IntegerOrInfinity::Integer(x) if (1..=100).contains(&x) => x as usize, _ => { + // 5 return context.throw_range_error( "precision must be an integer at least 1 and no greater than 100", ) @@ -343,10 +347,12 @@ impl Number { }; let precision_i32 = precision as i32; + // 7 let mut prefix = String::new(); // spec: "s" let mut suffix: String; // spec: "m" let exponent: i32; // spec: "e" + // 8 if this_num < 0.0 { prefix.push('-'); this_num = -this_num; @@ -354,9 +360,11 @@ impl Number { let mut is_scientific = false; + // 9 if this_num == 0.0 { suffix = "0".repeat(precision); exponent = 0; + // 10 } else { let mut buffer = ryu_js::Buffer::new(); suffix = buffer.format(this_num).to_string(); @@ -384,13 +392,16 @@ impl Number { } } + // 11 let e_inc = exponent + 1; if !is_scientific && e_inc != precision_i32 { + // 12 if exponent >= 0 { suffix.truncate(precision); if precision > e_inc as usize { suffix.insert(e_inc as usize, '.'); } + // 13 } else { prefix.push('0'); prefix.push('.'); @@ -398,6 +409,7 @@ impl Number { } } + // 14 Ok(Value::from(prefix + &suffix)) } diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index 51a24b1e8c8..24760223fd3 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -129,23 +129,29 @@ fn to_locale_string() { fn to_precision() { let mut context = Context::new(); let init = r#" + var infinity = (1/0).toPrecision(3); var default_precision = Number().toPrecision(); - var low_precision = Number(123456789).toPrecision(1); - var more_precision = Number(123456789).toPrecision(4); - var exact_precision = Number(123456789).toPrecision(9); - var over_precision = Number(123456789).toPrecision(50); - var neg_precision = Number(-123456789).toPrecision(4); + var explicit_ud_precision = Number().toPrecision(undefined); + var low_precision = (123456789).toPrecision(1); + var more_precision = (123456789).toPrecision(4); + var exact_precision = (123456789).toPrecision(9); + var over_precision = (123456789).toPrecision(50); + var neg_precision = (-123456789).toPrecision(4); "#; eprintln!("{}", forward(&mut context, init)); + let infinity = forward(&mut context, "infinity"); let default_precision = forward(&mut context, "default_precision"); + let explicit_ud_precision = forward(&mut context, "explicit_ud_precision"); let low_precision = forward(&mut context, "low_precision"); let more_precision = forward(&mut context, "more_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"); + assert_eq!(infinity, String::from("\"Infinity\"")); assert_eq!(default_precision, String::from("\"0\"")); + assert_eq!(explicit_ud_precision, String::from("\"0\"")); assert_eq!(low_precision, String::from("\"1e+8\"")); assert_eq!(more_precision, String::from("\"1.235e+8\"")); assert_eq!(exact_precision, String::from("\"123456789\"")); @@ -154,6 +160,18 @@ fn to_precision() { over_precision, String::from("\"123456789.00000000000000000000000000000000000000000\"") ); + + let expected = "Uncaught \"RangeError\": \"precision must be an integer at least 1 and no greater than 100\""; + + let range_error_1 = r#"(1).toPrecision(101);"#; + let range_error_2 = r#"(1).toPrecision(0);"#; + let range_error_3 = r#"(1).toPrecision(-2000);"#; + let range_error_4 = r#"(1).toPrecision('%');"#; + + assert_eq!(forward(&mut context, range_error_1), expected); + assert_eq!(forward(&mut context, range_error_2), expected); + assert_eq!(forward(&mut context, range_error_3), expected); + assert_eq!(forward(&mut context, range_error_4), expected); } #[test] From 77c27ae1db9be79288813b6e38bad2d4448b3c4a Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 14:59:34 +0100 Subject: [PATCH 5/8] Improved code conformance to rustfmt --- 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 ef0427fdf4e..c517a3cf2de 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -342,7 +342,7 @@ impl Number { // 5 return context.throw_range_error( "precision must be an integer at least 1 and no greater than 100", - ) + ); } }; let precision_i32 = precision as i32; From e3cb3c3146dec8ef8741680f28cc9c3ffed3346e Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 16:31:02 +0100 Subject: [PATCH 6/8] Documented flt_str_to_exp & round_to_precision utility functions --- boa/src/builtins/number/mod.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index c517a3cf2de..e44c4f27019 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -269,12 +269,17 @@ impl Number { Ok(Value::from(this_str_num)) } + /// flt_str_to_exp - used in to_precision + /// + /// This function traverses a string representing a number, + /// returning the floored log10 of this number. + /// fn flt_str_to_exp(flt: &str) -> i32 { - let mut nz_encountered = false; + let mut non_zero_encountered = false; let mut dot_encountered = false; for (i, c) in flt.chars().enumerate() { if c == '.' { - if nz_encountered { + if non_zero_encountered { return (i as i32) - 1; } dot_encountered = true; @@ -282,12 +287,23 @@ impl Number { if dot_encountered { return 1 - (i as i32); } - nz_encountered = true; + non_zero_encountered = true; } } (flt.len() as i32) - 1 } + /// round_to_precision - used in to_precision + /// + /// This procedure has two roles: + /// - If there are enough or more than enough digits in the + /// string to show the required precision, the number + /// represented by these digits is rounded using string + /// manipulation. + /// - Else, zeroes are appended to the string. + /// + /// When this procedure returns, `digits` is exactly `precision` long. + /// fn round_to_precision(digits: &mut String, precision: usize) { if digits.len() > precision { let to_round = digits.split_off(precision); From 66be2f80abe32b0736bc1f13fa46af5491389893 Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 16:32:47 +0100 Subject: [PATCH 7/8] Fixed bad arguments handling in Number.prototype.toPrecision --- 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 e44c4f27019..199c4f6a7f4 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -352,7 +352,7 @@ impl Number { } // 3 - let precision = match precision_var.to_integer_or_infinity(context).unwrap() { + let precision = match precision_var.to_integer_or_infinity(context)? { IntegerOrInfinity::Integer(x) if (1..=100).contains(&x) => x as usize, _ => { // 5 From f8c1abed84e4caed2b16c59b88d10d0cfe620e54 Mon Sep 17 00:00:00 2001 From: NathanRoyer Date: Wed, 16 Dec 2020 16:34:54 +0100 Subject: [PATCH 8/8] Improved implementation of Number.prototype.toPrecision by simplifying, commenting and restructuring the code --- boa/src/builtins/number/mod.rs | 49 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 199c4f6a7f4..3b7c10e31af 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -364,9 +364,9 @@ impl Number { let precision_i32 = precision as i32; // 7 - let mut prefix = String::new(); // spec: "s" - let mut suffix: String; // spec: "m" - let exponent: i32; // spec: "e" + let mut prefix = String::new(); // spec: 's' + let mut suffix: String; // spec: 'm' + let exponent: i32; // spec: 'e' // 8 if this_num < 0.0 { @@ -374,55 +374,64 @@ impl Number { this_num = -this_num; } - let mut is_scientific = false; - // 9 if this_num == 0.0 { suffix = "0".repeat(precision); exponent = 0; // 10 } 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(); + // a: getting an exponent exponent = Self::flt_str_to_exp(&suffix); + // b: getting relevant digits only if exponent < 0 { suffix = suffix.split_off((1 - exponent) as usize); } else if let Some(n) = suffix.find('.') { suffix.remove(n); } + // impl: having exactly `precision` digits in `suffix` Self::round_to_precision(&mut suffix, precision); + // c: switching to scientific notation let great_exp = exponent >= precision_i32; if exponent < -6 || great_exp { - is_scientific = true; - suffix.truncate(precision); + // ii if precision > 1 { suffix.insert(1, '.'); } + // vi suffix.push('e'); + // iii if great_exp { suffix.push('+'); } + // iv, v suffix.push_str(&exponent.to_string()); + + return Ok(Value::from(prefix + &suffix)); } } // 11 let e_inc = exponent + 1; - if !is_scientific && e_inc != precision_i32 { - // 12 - if exponent >= 0 { - suffix.truncate(precision); - if precision > e_inc as usize { - suffix.insert(e_inc as usize, '.'); - } - // 13 - } else { - prefix.push('0'); - prefix.push('.'); - prefix.push_str(&"0".repeat(-e_inc as usize)); - } + if e_inc == precision_i32 { + return Ok(Value::from(prefix + &suffix)); + } + + // 12 + if exponent >= 0 { + suffix.insert(e_inc as usize, '.'); + // 13 + } else { + prefix.push('0'); + prefix.push('.'); + prefix.push_str(&"0".repeat(-e_inc as usize)); } // 14