diff --git a/common/src/format.rs b/common/src/format.rs index 0e8a7bd927..8b42268773 100644 --- a/common/src/format.rs +++ b/common/src/format.rs @@ -312,13 +312,20 @@ impl FormatSpec { inter: i32, sep: char, disp_digit_cnt: i32, + use_whitespace_padding: bool, ) -> String { // Don't add separators to the floating decimal point of numbers let mut parts = magnitude_str.splitn(2, '.'); let magnitude_int_str = parts.next().unwrap().to_string(); let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32; let int_digit_cnt = disp_digit_cnt - dec_digit_cnt; - let mut result = FormatSpec::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt); + let mut result = FormatSpec::separate_integer( + magnitude_int_str, + inter, + sep, + int_digit_cnt, + use_whitespace_padding, + ); if let Some(part) = parts.next() { result.push_str(&format!(".{part}")) } @@ -330,6 +337,7 @@ impl FormatSpec { inter: i32, sep: char, disp_digit_cnt: i32, + use_whitespace_padding: bool, ) -> String { let magnitude_len = magnitude_str.len() as i32; let offset = (disp_digit_cnt % (inter + 1) == 0) as i32; @@ -338,7 +346,10 @@ impl FormatSpec { if pad_cnt > 0 { // separate with 0 padding let sep_cnt = disp_digit_cnt / (inter + 1); - let padding = "0".repeat((pad_cnt - sep_cnt) as usize); + let padding = match use_whitespace_padding { + true => " ".repeat((pad_cnt - sep_cnt) as usize), + false => "0".repeat((pad_cnt - sep_cnt) as usize), + }; let padded_num = format!("{padding}{magnitude_str}"); FormatSpec::insert_separator(padded_num, inter, sep, sep_cnt) } else { @@ -391,7 +402,12 @@ impl FormatSpec { } } - fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String { + fn add_magnitude_separators( + &self, + magnitude_str: String, + prefix: &str, + use_whitespace_padding: bool, + ) -> String { match &self.grouping_option { Some(fg) => { let sep = match fg { @@ -407,6 +423,7 @@ impl FormatSpec { inter, sep, disp_digit_cnt, + use_whitespace_padding, ) } None => magnitude_str, @@ -433,7 +450,32 @@ impl FormatSpec { let ch = char::from(self.format_type.as_ref().unwrap()); Err(FormatSpecError::UnknownFormatCode(ch, "float")) } - Some(FormatType::Number) => Err(FormatSpecError::NotImplemented('n', "float")), + Some(FormatType::Number) => { + const LOWER_BOUND: f64 = 100000.0; + let show_as_exponential = magnitude >= LOWER_BOUND; + let magnitude_str = match show_as_exponential { + true => float_ops::format_exponent( + 0, + magnitude, + float_ops::Case::Lower, + self.alternate_form, + ), + false => magnitude.to_string(), + }; + + let magnitude_len = magnitude_str.len(); + let width = self.width.unwrap_or(magnitude_len); + let disp_digit_cnt = cmp::max(width, magnitude_len); + let inter = self.get_separator_interval(); + + Ok(FormatSpec::add_magnitude_separators_for_char( + magnitude_str, + inter.try_into().unwrap(), + '\0', + disp_digit_cnt.try_into().unwrap(), + true, + )) + } Some(FormatType::GeneralFormat(case)) => { let precision = if precision == 0 { 1 } else { precision }; Ok(float_ops::format_general( @@ -487,7 +529,7 @@ impl FormatSpec { FormatSign::MinusOrSpace => " ", } }; - let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, sign_str); + let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, sign_str, false); self.format_sign_and_align( unsafe { &BorrowedStr::from_ascii_unchecked(magnitude_str.as_bytes()) }, sign_str, @@ -559,7 +601,7 @@ impl FormatSpec { }, }; let sign_prefix = format!("{sign_str}{prefix}"); - let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, &sign_prefix); + let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, &sign_prefix, false); self.format_sign_and_align( &BorrowedStr::from_bytes(magnitude_str.as_bytes()), &sign_prefix, diff --git a/extra_tests/snippets/builtin_str.py b/extra_tests/snippets/builtin_str.py index 12060fc40a..9bbf33c9a4 100644 --- a/extra_tests/snippets/builtin_str.py +++ b/extra_tests/snippets/builtin_str.py @@ -609,6 +609,26 @@ def try_mutate_str(): assert '{:g}'.format(1.020e-13) == '1.02e-13' assert "{:g}".format(1.020e-4) == '0.000102' +# Test n formatting +assert '{:n}'.format(1) == '1' +assert '{:2n}'.format(1) == ' 1' +assert '{:3n}'.format(1) == ' 1' +assert '{:4n}'.format(1) == ' 1' +assert '{:5n}'.format(1) == ' 1' +assert '{:n}'.format(10) == '10' +assert '{:2n}'.format(10) == '10' +assert '{:3n}'.format(10) == ' 10' +assert '{:4n}'.format(10) == ' 10' +assert '{:5n}'.format(10) == ' 10' +assert '{:n}'.format(1000000.1234) == '1e+06' +assert '{:7n}'.format(1000000.1234) == ' 1e+06' +assert '{:.8n}'.format(1000000.1234) == ' 1e+06' +assert '{:n}'.format(float('inf')) == 'inf' +assert '{:2n}'.format(float('inf')) == 'inf' +assert '{:3n}'.format(float('inf')) == 'inf' +assert '{:4n}'.format(float('inf')) == ' inf' +assert '{:5n}'.format(float('inf')) == ' inf' + # remove*fix test def test_removeprefix(): s = 'foobarfoo'