From dc3a30066ba746c463bb6a4e43578e6b7dc0e622 Mon Sep 17 00:00:00 2001 From: ChJR Date: Sat, 31 Oct 2020 22:58:46 +0900 Subject: [PATCH 01/17] Unify format_float_as_exponent to clarify --- common/src/float_ops.rs | 17 +++++++++++++++++ vm/src/cformat.rs | 23 +++-------------------- vm/src/format.rs | 26 ++++++-------------------- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index f4089e064..8b1ac8633 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -83,6 +83,23 @@ pub fn is_integer(v: f64) -> bool { (v - v.round()).abs() < std::f64::EPSILON } +// Formats floats into Python style exponent notation, by first formatting in Rust style +// exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`). +pub fn format_float_as_exponent(precision: usize, magnitude: f64) -> String { + if magnitude.is_nan() { + return "nan".to_string(); + } + if magnitude.is_infinite() { + return "inf".to_string(); + } + + let r_exp = format!("{:.*e}", precision, magnitude); + let mut parts = r_exp.splitn(2, 'e'); + let base = parts.next().unwrap(); + let exponent = parts.next().unwrap().parse::().unwrap(); + format!("{}e{:+#03}", base, exponent) +} + pub fn to_string(value: f64) -> String { let lit = format!("{:e}", value); if let Some(position) = lit.find('e') { diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index ab9302165..2f87f0428 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -1,4 +1,5 @@ use crate::builtins::{float, int, pystr, tuple}; +use crate::common::float_ops; /// Implementation of Printf-Style string formatting /// [https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting] use crate::format::get_num_digits; @@ -242,24 +243,6 @@ impl CFormatSpec { } } - fn normalize_float(&self, num: f64) -> (f64, i32) { - let mut fraction = num; - let mut exponent = 0; - loop { - if fraction >= 10.0 { - fraction /= 10.0; - exponent += 1; - } else if fraction < 1.0 && fraction > 0.0 { - fraction *= 10.0; - exponent -= 1; - } else { - break; - } - } - - (fraction, exponent) - } - pub(crate) fn format_float(&self, num: f64) -> Result { let sign_string = if num.is_sign_positive() { self.flags.sign_string() @@ -281,8 +264,8 @@ impl CFormatSpec { Some(CFormatQuantity::Amount(p)) => p, _ => 6, }; - let (fraction, exponent) = self.normalize_float(num.abs()); - Ok(format!("{:.*}e{:+03}", precision, fraction, exponent)) + let magnitude = num.abs(); + Ok(float_ops::format_float_as_exponent(precision, magnitude)) } CFormatType::Float(CFloatType::General(_)) => { Err("Not yet implemented for %g and %G".to_owned()) diff --git a/vm/src/format.rs b/vm/src/format.rs index 963d7d018..042f7da83 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -277,16 +277,6 @@ fn parse_format_spec(text: &str) -> Result { }) } -// Formats floats into Python style exponent notation, by first formatting in Rust style -// exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`). -fn format_float_as_exponent(precision: usize, magnitude: f64, separator: &str) -> String { - let r_exp = format!("{:.*e}", precision, magnitude); - let mut parts = r_exp.splitn(2, 'e'); - let base = parts.next().unwrap(); - let exponent = parts.next().unwrap().parse::().unwrap(); - format!("{}{}+{:02}", base, separator, exponent) -} - impl FormatSpec { pub(crate) fn parse(text: &str) -> Result { parse_format_spec(text) @@ -385,16 +375,12 @@ impl FormatSpec { Some(FormatType::GeneralFormatLower) => { Err("Format code 'g' for object of type 'float' not implemented yet") } - Some(FormatType::ExponentUpper) => match magnitude { - magnitude if magnitude.is_nan() => Ok("NAN".to_owned()), - magnitude if magnitude.is_infinite() => Ok("INF".to_owned()), - _ => Ok(format_float_as_exponent(precision, magnitude, "E")), - }, - Some(FormatType::ExponentLower) => match magnitude { - magnitude if magnitude.is_nan() => Ok("nan".to_owned()), - magnitude if magnitude.is_infinite() => Ok("inf".to_owned()), - _ => Ok(format_float_as_exponent(precision, magnitude, "e")), - }, + Some(FormatType::ExponentUpper) => { + Ok(float_ops::format_float_as_exponent(precision, magnitude).to_uppercase()) + } + Some(FormatType::ExponentLower) => { + Ok(float_ops::format_float_as_exponent(precision, magnitude).to_lowercase()) + } Some(FormatType::Percentage) => match magnitude { magnitude if magnitude.is_nan() => Ok("nan%".to_owned()), magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()), From 29859b47e4bd010dc17b71a89f5d86c7565222f7 Mon Sep 17 00:00:00 2001 From: ChJR Date: Sat, 31 Oct 2020 23:02:38 +0900 Subject: [PATCH 02/17] Separate Formatting decimal point case in cformat --- vm/src/cformat.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 2f87f0428..a45a57c27 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -75,7 +75,7 @@ enum CNumberType { #[derive(Debug, PartialEq)] enum CFloatType { Exponent(CFormatCase), - PointDecimal, + PointDecimal(CFormatCase), General(CFormatCase), } @@ -251,7 +251,7 @@ impl CFormatSpec { }; let magnitude_string = match self.format_type { - CFormatType::Float(CFloatType::PointDecimal) => { + CFormatType::Float(CFloatType::PointDecimal(_)) => { let precision = match self.precision { Some(CFormatQuantity::Amount(p)) => p, _ => 6, @@ -747,12 +747,12 @@ fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErr next_char.unwrap(), )), Some('f') => Ok(( - CFormatType::Float(PointDecimal), + CFormatType::Float(PointDecimal(Lowercase)), chars.as_str(), next_char.unwrap(), )), Some('F') => Ok(( - CFormatType::Float(PointDecimal), + CFormatType::Float(PointDecimal(Uppercase)), chars.as_str(), next_char.unwrap(), )), From db976ddc702e1a4dce5eb01601112ab03007a6d1 Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 00:57:32 +0900 Subject: [PATCH 03/17] Clear up format_float_as_exponent --- common/src/float_ops.rs | 48 ++++++++++++++++++++++++++++++----------- vm/src/cformat.rs | 12 ++++++++--- vm/src/format.rs | 16 ++++++++------ 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 8b1ac8633..034b9dbf9 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -83,21 +83,45 @@ pub fn is_integer(v: f64) -> bool { (v - v.round()).abs() < std::f64::EPSILON } +#[derive(Debug)] +pub enum FloatFormatCase { + Lower, + Upper, +} + +fn format_nan(case: FloatFormatCase) -> String { + let nan = match case { + FloatFormatCase::Lower => "nan", + FloatFormatCase::Upper => "NAN", + }; + + nan.to_string() +} + +fn format_inf(case: FloatFormatCase) -> String { + let inf = match case { + FloatFormatCase::Lower => "inf", + FloatFormatCase::Upper => "INF", + }; + + inf.to_string() +} + // Formats floats into Python style exponent notation, by first formatting in Rust style // exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`). -pub fn format_float_as_exponent(precision: usize, magnitude: f64) -> String { - if magnitude.is_nan() { - return "nan".to_string(); +pub fn format_float_as_exponent(precision: usize, magnitude: f64, case: FloatFormatCase) -> String { + match magnitude { + magnitude if magnitude.is_finite() => { + let r_exp = format!("{:.*e}", precision, magnitude); + let mut parts = r_exp.splitn(2, 'e'); + let base = parts.next().unwrap(); + let exponent = parts.next().unwrap().parse::().unwrap(); + format!("{}e{:+#03}", base, exponent) + } + magnitude if magnitude.is_nan() => format_nan(case), + magnitude if magnitude.is_infinite() => format_inf(case), + _ => "".to_string(), } - if magnitude.is_infinite() { - return "inf".to_string(); - } - - let r_exp = format!("{:.*e}", precision, magnitude); - let mut parts = r_exp.splitn(2, 'e'); - let base = parts.next().unwrap(); - let exponent = parts.next().unwrap().parse::().unwrap(); - format!("{}e{:+#03}", base, exponent) } pub fn to_string(value: f64) -> String { diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index a45a57c27..607951280 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -250,7 +250,7 @@ impl CFormatSpec { "-" }; - let magnitude_string = match self.format_type { + let magnitude_string = match &self.format_type { CFormatType::Float(CFloatType::PointDecimal(_)) => { let precision = match self.precision { Some(CFormatQuantity::Amount(p)) => p, @@ -259,13 +259,19 @@ impl CFormatSpec { let magnitude = num.abs(); Ok(format!("{:.*}", precision, magnitude)) } - CFormatType::Float(CFloatType::Exponent(_)) => { + CFormatType::Float(CFloatType::Exponent(case)) => { let precision = match self.precision { Some(CFormatQuantity::Amount(p)) => p, _ => 6, }; let magnitude = num.abs(); - Ok(float_ops::format_float_as_exponent(precision, magnitude)) + let float_case = match case { + CFormatCase::Lowercase => float_ops::FloatFormatCase::Lower, + CFormatCase::Uppercase => float_ops::FloatFormatCase::Upper, + }; + Ok(float_ops::format_float_as_exponent( + precision, magnitude, float_case, + )) } CFormatType::Float(CFloatType::General(_)) => { Err("Not yet implemented for %g and %G".to_owned()) diff --git a/vm/src/format.rs b/vm/src/format.rs index 042f7da83..3d58b5e26 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -375,12 +375,16 @@ impl FormatSpec { Some(FormatType::GeneralFormatLower) => { Err("Format code 'g' for object of type 'float' not implemented yet") } - Some(FormatType::ExponentUpper) => { - Ok(float_ops::format_float_as_exponent(precision, magnitude).to_uppercase()) - } - Some(FormatType::ExponentLower) => { - Ok(float_ops::format_float_as_exponent(precision, magnitude).to_lowercase()) - } + Some(FormatType::ExponentUpper) => Ok(float_ops::format_float_as_exponent( + precision, + magnitude, + float_ops::FloatFormatCase::Upper, + )), + Some(FormatType::ExponentLower) => Ok(float_ops::format_float_as_exponent( + precision, + magnitude, + float_ops::FloatFormatCase::Lower, + )), Some(FormatType::Percentage) => match magnitude { magnitude if magnitude.is_nan() => Ok("nan%".to_owned()), magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()), From e95469e1c5458ece36d19c09dbfc20c7cdf770ff Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 01:32:54 +0900 Subject: [PATCH 04/17] Refine names for shorter code --- common/src/float_ops.rs | 16 ++++++++-------- vm/src/cformat.rs | 6 +++--- vm/src/format.rs | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 034b9dbf9..ab3c599fc 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -84,24 +84,24 @@ pub fn is_integer(v: f64) -> bool { } #[derive(Debug)] -pub enum FloatFormatCase { +pub enum Case { Lower, Upper, } -fn format_nan(case: FloatFormatCase) -> String { +fn format_nan(case: Case) -> String { let nan = match case { - FloatFormatCase::Lower => "nan", - FloatFormatCase::Upper => "NAN", + Case::Lower => "nan", + Case::Upper => "NAN", }; nan.to_string() } -fn format_inf(case: FloatFormatCase) -> String { +fn format_inf(case: Case) -> String { let inf = match case { - FloatFormatCase::Lower => "inf", - FloatFormatCase::Upper => "INF", + Case::Lower => "inf", + Case::Upper => "INF", }; inf.to_string() @@ -109,7 +109,7 @@ fn format_inf(case: FloatFormatCase) -> String { // Formats floats into Python style exponent notation, by first formatting in Rust style // exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`). -pub fn format_float_as_exponent(precision: usize, magnitude: f64, case: FloatFormatCase) -> String { +pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { match magnitude { magnitude if magnitude.is_finite() => { let r_exp = format!("{:.*e}", precision, magnitude); diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 1b634f654..396b2257e 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -329,11 +329,11 @@ impl CFormatSpec { } CFormatType::Float(CFloatType::Exponent(case)) => { let case = match case { - CFormatCase::Lowercase => float_ops::FloatFormatCase::Lower, - CFormatCase::Uppercase => float_ops::FloatFormatCase::Upper, + CFormatCase::Lowercase => float_ops::Case::Lower, + CFormatCase::Uppercase => float_ops::Case::Upper, }; let magnitude = num.abs(); - float_ops::format_float_as_exponent(precision, magnitude, case) + float_ops::format_exponent(precision, magnitude, case) } CFormatType::Float(CFloatType::General(case)) => { let precision = if precision == 0 { 1 } else { precision }; diff --git a/vm/src/format.rs b/vm/src/format.rs index 3d58b5e26..af552593d 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -375,15 +375,15 @@ impl FormatSpec { Some(FormatType::GeneralFormatLower) => { Err("Format code 'g' for object of type 'float' not implemented yet") } - Some(FormatType::ExponentUpper) => Ok(float_ops::format_float_as_exponent( + Some(FormatType::ExponentUpper) => Ok(float_ops::format_exponent( precision, magnitude, - float_ops::FloatFormatCase::Upper, + float_ops::Case::Upper, )), - Some(FormatType::ExponentLower) => Ok(float_ops::format_float_as_exponent( + Some(FormatType::ExponentLower) => Ok(float_ops::format_exponent( precision, magnitude, - float_ops::FloatFormatCase::Lower, + float_ops::Case::Lower, )), Some(FormatType::Percentage) => match magnitude { magnitude if magnitude.is_nan() => Ok("nan%".to_owned()), From 6ca53213e8c97eb5682a3e4f89bab311b5c597ee Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 01:48:43 +0900 Subject: [PATCH 05/17] Fix a mistake --- common/src/float_ops.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index ab3c599fc..c0e7a359b 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -116,7 +116,11 @@ pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { let mut parts = r_exp.splitn(2, 'e'); let base = parts.next().unwrap(); let exponent = parts.next().unwrap().parse::().unwrap(); - format!("{}e{:+#03}", base, exponent) + let e = match case { + Case::Lower => 'e', + Case::Upper => 'E', + }; + format!("{}{}{:+#03}", base, e, exponent) } magnitude if magnitude.is_nan() => format_nan(case), magnitude if magnitude.is_infinite() => format_inf(case), From 8c96bd680fa899c3e548ee2be382405b4a955ae2 Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 01:56:08 +0900 Subject: [PATCH 06/17] Unify general format of floating numbers --- common/src/float_ops.rs | 23 +++++++++++++++++++++++ vm/src/cformat.rs | 35 ++++++----------------------------- vm/src/format.rs | 16 ++++++++++------ 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index c0e7a359b..e54513ad6 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -128,6 +128,29 @@ pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { } } +pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { + match magnitude { + magnitude if magnitude.is_finite() => { + let r_exp = format!("{:.*e}", precision, magnitude); + let mut parts = r_exp.splitn(2, 'e'); + let base = parts.next().unwrap(); + let exponent = parts.next().unwrap().parse::().unwrap(); + if exponent < -4 || exponent >= (precision as i64) { + let e = match case { + Case::Lower => 'e', + Case::Upper => 'E', + }; + format!("{}{}{:+#03}", base, e, exponent) + } else { + format!("{}", magnitude) + } + } + magnitude if magnitude.is_nan() => format_nan(case), + magnitude if magnitude.is_infinite() => format_inf(case), + _ => "".to_string(), + } +} + pub fn to_string(value: f64) -> String { let lit = format!("{:e}", value); if let Some(position) = lit.find('e') { diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 396b2257e..fe1aeb81a 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -293,24 +293,6 @@ impl CFormatSpec { } } - fn normalize_float(&self, num: f64) -> (f64, i32) { - let mut fraction = num; - let mut exponent = 0; - loop { - if fraction >= 10.0 { - fraction /= 10.0; - exponent += 1; - } else if fraction < 1.0 && fraction > 0.0 { - fraction *= 10.0; - exponent -= 1; - } else { - break; - } - } - - (fraction, exponent) - } - pub(crate) fn format_float(&self, num: f64) -> String { let sign_string = if num.is_sign_positive() { self.flags.sign_string() @@ -337,17 +319,12 @@ impl CFormatSpec { } CFormatType::Float(CFloatType::General(case)) => { let precision = if precision == 0 { 1 } else { precision }; - let (fraction, exponent) = self.normalize_float(num.abs()); - if exponent < -4 || exponent >= (precision as i32) { - let case = match case { - CFormatCase::Lowercase => 'e', - CFormatCase::Uppercase => 'E', - }; - format!("{}{}{:+03}", fraction, case, exponent) - } else { - let magnitude = num.abs(); - format!("{}", magnitude) - } + let case = match case { + CFormatCase::Lowercase => float_ops::Case::Lower, + CFormatCase::Uppercase => float_ops::Case::Upper, + }; + let magnitude = num.abs(); + float_ops::format_general(precision, magnitude, case) } _ => unreachable!(), }; diff --git a/vm/src/format.rs b/vm/src/format.rs index af552593d..13b97858c 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -369,12 +369,16 @@ impl FormatSpec { Some(FormatType::Number) => { Err("Format code 'n' for object of type 'float' not implemented yet") } - Some(FormatType::GeneralFormatUpper) => { - Err("Format code 'G' for object of type 'float' not implemented yet") - } - Some(FormatType::GeneralFormatLower) => { - Err("Format code 'g' for object of type 'float' not implemented yet") - } + Some(FormatType::GeneralFormatUpper) => Ok(float_ops::format_general( + precision, + magnitude, + float_ops::Case::Upper, + )), + Some(FormatType::GeneralFormatLower) => Ok(float_ops::format_general( + precision, + magnitude, + float_ops::Case::Lower, + )), Some(FormatType::ExponentUpper) => Ok(float_ops::format_exponent( precision, magnitude, From 07a857b4adfe9f6de4660b39187e95e066f4681c Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 02:30:21 +0900 Subject: [PATCH 07/17] Add tests for formatting floating numbers --- extra_tests/snippets/strings.py | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/extra_tests/snippets/strings.py b/extra_tests/snippets/strings.py index 870934a80..d41a395c5 100644 --- a/extra_tests/snippets/strings.py +++ b/extra_tests/snippets/strings.py @@ -232,6 +232,62 @@ assert "%e" % 0 == '0.000000e+00' assert "%e" % 0.1 == '1.000000e-01' assert "%e" % 10 == '1.000000e+01' assert "%.10e" % 1.2345678901234567890 == '1.2345678901e+00' +assert '%e' % float('nan') == 'nan' +assert '%e' % float('-nan') == 'nan' +assert '%E' % float('nan') == 'NAN' +assert '%e' % float('inf') == 'inf' +assert '%e' % float('-inf') == '-inf' +assert '%E' % float('inf') == 'INF' +assert "%g" % 123456.78901234567890 == '123457' +assert "%.0g" % 123456.78901234567890 == '1e+05' +assert "%.1g" % 123456.78901234567890 == '1e+05' +assert "%.2g" % 123456.78901234567890 == '1.2e+05' +assert "%g" % 1234567.8901234567890 == '1.23457e+06' +assert "%.0g" % 1234567.8901234567890 == '1e+06' +assert "%.1g" % 1234567.8901234567890 == '1e+06' +assert "%.2g" % 1234567.8901234567890 == '1.2e+06' +assert "%.3g" % 1234567.8901234567890 == '1.23e+06' +assert "%.5g" % 1234567.8901234567890 == '1.2346e+06' +assert "%.6g" % 1234567.8901234567890 == '1.23457e+06' +assert "%.7g" % 1234567.8901234567890 == '1234568' +assert "%.8g" % 1234567.8901234567890 == '1234567.9' +assert "%G" % 123456.78901234567890 == '123457' +assert "%.0G" % 123456.78901234567890 == '1E+05' +assert "%.1G" % 123456.78901234567890 == '1E+05' +assert "%.2G" % 123456.78901234567890 == '1.2E+05' +assert "%G" % 1234567.8901234567890 == '1.23457E+06' +assert "%.0G" % 1234567.8901234567890 == '1E+06' +assert "%.1G" % 1234567.8901234567890 == '1E+06' +assert "%.2G" % 1234567.8901234567890 == '1.2E+06' +assert "%.3G" % 1234567.8901234567890 == '1.23E+06' +assert "%.5G" % 1234567.8901234567890 == '1.2346E+06' +assert "%.6G" % 1234567.8901234567890 == '1.23457E+06' +assert "%.7G" % 1234567.8901234567890 == '1234568' +assert "%.8G" % 1234567.8901234567890 == '1234567.9' +assert '%g' % 0.12345678901234567890 == '0.123457' +assert '%g' % 0.12345678901234567890e-1 == '0.0123457' +assert '%g' % 0.12345678901234567890e-2 == '0.00123457' +assert '%g' % 0.12345678901234567890e-3 == '0.000123457' +assert '%g' % 0.12345678901234567890e-4 == '1.23457e-05' +assert '%g' % 0.12345678901234567890e-5 == '1.23457e-06' +assert '%.6g' % 0.12345678901234567890e-5 == '1.23457e-06' +assert '%.10g' % 0.12345678901234567890e-5 == '1.23456789e-06' +assert '%.20g' % 0.12345678901234567890e-5 == '1.2345678901234567384e-06' +assert '%G' % 0.12345678901234567890 == '0.123457' +assert '%G' % 0.12345678901234567890E-1 == '0.0123457' +assert '%G' % 0.12345678901234567890E-2 == '0.00123457' +assert '%G' % 0.12345678901234567890E-3 == '0.000123457' +assert '%G' % 0.12345678901234567890E-4 == '1.23457E-05' +assert '%G' % 0.12345678901234567890E-5 == '1.23457E-06' +assert '%.6G' % 0.12345678901234567890E-5 == '1.23457E-06' +assert '%.10G' % 0.12345678901234567890E-5 == '1.23456789E-06' +assert '%.20G' % 0.12345678901234567890E-5 == '1.2345678901234567384E-06' +assert '%g' % float('nan') == 'nan' +assert '%g' % float('-nan') == 'nan' +assert '%G' % float('nan') == 'NAN' +assert '%g' % float('inf') == 'inf' +assert '%g' % float('-inf') == '-inf' +assert '%G' % float('inf') == 'INF' assert_raises(TypeError, lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25), _msg='format requires a mapping') assert_raises(TypeError, lambda: "My name is %(name)s" % "Foo", _msg='format requires a mapping') @@ -477,6 +533,57 @@ assert '{:e}'.format(float('inf')) == 'inf' assert '{:e}'.format(float('-inf')) == '-inf' assert '{:E}'.format(float('inf')) == 'INF' +# Test g & G formatting +assert '{:g}'.format(123456.78901234567890) == '123457' +assert '{:.0g}'.format(123456.78901234567890) == '1e+05' +assert '{:.1g}'.format(123456.78901234567890) == '1e+05' +assert '{:.2g}'.format(123456.78901234567890) == '1.2e+05' +assert '{:g}'.format(1234567.8901234567890) == '1.23457e+06' +assert '{:.0g}'.format(1234567.8901234567890) == '1e+06' +assert '{:.1g}'.format(1234567.8901234567890) == '1e+06' +assert '{:.2g}'.format(1234567.8901234567890) == '1.2e+06' +assert '{:.3g}'.format(1234567.8901234567890) == '1.23e+06' +assert '{:.5g}'.format(1234567.8901234567890) == '1.2346e+06' +assert '{:.6g}'.format(1234567.8901234567890) == '1.23457e+06' +assert '{:.7g}'.format(1234567.8901234567890) == '1234568' +assert '{:.8g}'.format(1234567.8901234567890) == '1234567.9' +assert '{:G}'.format(123456.78901234567890) == '123457' +assert '{:.0G}'.format(123456.78901234567890) == '1E+05' +assert '{:.1G}'.format(123456.78901234567890) == '1E+05' +assert '{:.2G}'.format(123456.78901234567890) == '1.2E+05' +assert '{:G}'.format(1234567.8901234567890) == '1.23457E+06' +assert '{:.0G}'.format(1234567.8901234567890) == '1E+06' +assert '{:.1G}'.format(1234567.8901234567890) == '1E+06' +assert '{:.2G}'.format(1234567.8901234567890) == '1.2E+06' +assert '{:.3G}'.format(1234567.8901234567890) == '1.23E+06' +assert '{:.5G}'.format(1234567.8901234567890) == '1.2346E+06' +assert '{:.6G}'.format(1234567.8901234567890) == '1.23457E+06' +assert '{:.7G}'.format(1234567.8901234567890) == '1234568' +assert '{:.8G}'.format(1234567.8901234567890) == '1234567.9' +assert '{:g}'.format(0.12345678901234567890) == '0.123457' +assert '{:g}'.format(0.12345678901234567890e-1) == '0.0123457' +assert '{:g}'.format(0.12345678901234567890e-2) == '0.00123457' +assert '{:g}'.format(0.12345678901234567890e-3) == '0.000123457' +assert '{:g}'.format(0.12345678901234567890e-4) == '1.23457e-05' +assert '{:g}'.format(0.12345678901234567890e-5) == '1.23457e-06' +assert '{:.6g}'.format(0.12345678901234567890e-5) == '1.23457e-06' +assert '{:.10g}'.format(0.12345678901234567890e-5) == '1.23456789e-06' +assert '{:.20g}'.format(0.12345678901234567890e-5) == '1.2345678901234567384e-06' +assert '{:G}'.format(0.12345678901234567890) == '0.123457' +assert '{:G}'.format(0.12345678901234567890E-1) == '0.0123457' +assert '{:G}'.format(0.12345678901234567890E-2) == '0.00123457' +assert '{:G}'.format(0.12345678901234567890E-3) == '0.000123457' +assert '{:G}'.format(0.12345678901234567890E-4) == '1.23457E-05' +assert '{:G}'.format(0.12345678901234567890E-5) == '1.23457E-06' +assert '{:.6G}'.format(0.12345678901234567890E-5) == '1.23457E-06' +assert '{:.10G}'.format(0.12345678901234567890E-5) == '1.23456789E-06' +assert '{:.20G}'.format(0.12345678901234567890E-5) == '1.2345678901234567384E-06' +assert '{:g}'.format(float('nan')) == 'nan' +assert '{:g}'.format(float('-nan')) == 'nan' +assert '{:G}'.format(float('nan')) == 'NAN' +assert '{:g}'.format(float('inf')) == 'inf' +assert '{:g}'.format(float('-inf')) == '-inf' +assert '{:G}'.format(float('inf')) == 'INF' # remove*fix test def test_removeprefix(): From 213a335bcf595ac42a88024ac2268c21d4be258e Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 02:42:46 +0900 Subject: [PATCH 08/17] Unify fixed-point format of floating numbers --- common/src/float_ops.rs | 9 +++++++++ vm/src/cformat.rs | 8 ++++++-- vm/src/format.rs | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index e54513ad6..21787eeb7 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -107,6 +107,15 @@ fn format_inf(case: Case) -> String { inf.to_string() } +pub fn format_fixed(precision: usize, magnitude: f64, case: Case) -> String { + match magnitude { + magnitude if magnitude.is_finite() => format!("{:.*}", precision, magnitude), + magnitude if magnitude.is_nan() => format_nan(case), + magnitude if magnitude.is_infinite() => format_inf(case), + _ => "".to_string(), + } +} + // Formats floats into Python style exponent notation, by first formatting in Rust style // exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`). pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index fe1aeb81a..5e1bf0fca 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -305,9 +305,13 @@ impl CFormatSpec { }; let magnitude_string = match &self.format_type { - CFormatType::Float(CFloatType::PointDecimal(_)) => { + CFormatType::Float(CFloatType::PointDecimal(case)) => { + let case = match case { + CFormatCase::Lowercase => float_ops::Case::Lower, + CFormatCase::Uppercase => float_ops::Case::Upper, + }; let magnitude = num.abs(); - format!("{:.*}", precision, magnitude) + float_ops::format_fixed(precision, magnitude, case) } CFormatType::Float(CFloatType::Exponent(case)) => { let case = match case { diff --git a/vm/src/format.rs b/vm/src/format.rs index 13b97858c..9571035b5 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -347,16 +347,16 @@ impl FormatSpec { let precision = self.precision.unwrap_or(6); let magnitude = num.abs(); let raw_magnitude_string_result: Result = match self.format_type { - Some(FormatType::FixedPointUpper) => match magnitude { - magnitude if magnitude.is_nan() => Ok("NAN".to_owned()), - magnitude if magnitude.is_infinite() => Ok("INF".to_owned()), - _ => Ok(format!("{:.*}", precision, magnitude)), - }, - Some(FormatType::FixedPointLower) => match magnitude { - magnitude if magnitude.is_nan() => Ok("nan".to_owned()), - magnitude if magnitude.is_infinite() => Ok("inf".to_owned()), - _ => Ok(format!("{:.*}", precision, magnitude)), - }, + Some(FormatType::FixedPointUpper) => Ok(float_ops::format_fixed( + precision, + magnitude, + float_ops::Case::Upper, + )), + Some(FormatType::FixedPointLower) => Ok(float_ops::format_fixed( + precision, + magnitude, + float_ops::Case::Lower, + )), Some(FormatType::Decimal) => Err("Unknown format code 'd' for object of type 'float'"), Some(FormatType::Binary) => Err("Unknown format code 'b' for object of type 'float'"), Some(FormatType::Octal) => Err("Unknown format code 'o' for object of type 'float'"), From 31d662eded4c4472ba6c19ebb0f4133f1f89a60a Mon Sep 17 00:00:00 2001 From: ChJR Date: Tue, 10 Nov 2020 02:50:10 +0900 Subject: [PATCH 09/17] Fix nan sign bug in cformat --- vm/src/cformat.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 5e1bf0fca..0fbd78dad 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -294,11 +294,12 @@ impl CFormatSpec { } pub(crate) fn format_float(&self, num: f64) -> String { - let sign_string = if num.is_sign_positive() { - self.flags.sign_string() - } else { + let sign_string = if num.is_sign_negative() && !num.is_nan() { "-" + } else { + self.flags.sign_string() }; + let precision = match self.precision { Some(CFormatQuantity::Amount(p)) => p, _ => 6, From 245ed70e4f8d353eb31b87e0f65e48be34e4716a Mon Sep 17 00:00:00 2001 From: ChJR Date: Thu, 19 Nov 2020 23:07:54 +0900 Subject: [PATCH 10/17] Add tests for general formatting of floating numbers Check for trailing zeros. --- extra_tests/snippets/strings.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extra_tests/snippets/strings.py b/extra_tests/snippets/strings.py index d41a395c5..6fbc64c03 100644 --- a/extra_tests/snippets/strings.py +++ b/extra_tests/snippets/strings.py @@ -288,6 +288,16 @@ assert '%G' % float('nan') == 'NAN' assert '%g' % float('inf') == 'inf' assert '%g' % float('-inf') == '-inf' assert '%G' % float('inf') == 'INF' +assert "%.0g" % 1.020e-13 == '1e-13' +assert "%.0g" % 1.020e-13 == '1e-13' +assert "%.1g" % 1.020e-13 == '1e-13' +assert "%.2g" % 1.020e-13 == '1e-13' +assert "%.3g" % 1.020e-13 == '1.02e-13' +assert "%.4g" % 1.020e-13 == '1.02e-13' +assert "%.5g" % 1.020e-13 == '1.02e-13' +assert "%.6g" % 1.020e-13 == '1.02e-13' +assert "%.7g" % 1.020e-13 == '1.02e-13' +assert "%g" % 1.020e-13 == '1.02e-13' assert_raises(TypeError, lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25), _msg='format requires a mapping') assert_raises(TypeError, lambda: "My name is %(name)s" % "Foo", _msg='format requires a mapping') From cf2aa7448df9534c47f78f3ff306b91ceac3db74 Mon Sep 17 00:00:00 2001 From: ChJR Date: Thu, 19 Nov 2020 23:21:40 +0900 Subject: [PATCH 11/17] Fix trailing zeros in general formatting of floating numbers --- common/src/float_ops.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 21787eeb7..13635425a 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -140,7 +140,7 @@ pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { match magnitude { magnitude if magnitude.is_finite() => { - let r_exp = format!("{:.*e}", precision, magnitude); + let r_exp = format!("{:.*e}", precision - 1, magnitude); let mut parts = r_exp.splitn(2, 'e'); let base = parts.next().unwrap(); let exponent = parts.next().unwrap().parse::().unwrap(); @@ -149,9 +149,16 @@ pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { Case::Lower => 'e', Case::Upper => 'E', }; + + let mut base = format!("{:.*}", precision + 1, base); + while base.ends_with('0') || base.ends_with('.') { + base.truncate(base.len() - 1); + } format!("{}{}{:+#03}", base, e, exponent) } else { - format!("{}", magnitude) + let precision = (precision as i64) - 1 - exponent; + let precision = precision as usize; + format!("{:.*}", precision, magnitude) } } magnitude if magnitude.is_nan() => format_nan(case), From 4f1232a0c2cfe30163e0809297df2555b8d7c46b Mon Sep 17 00:00:00 2001 From: ChJR Date: Fri, 20 Nov 2020 02:41:44 +0900 Subject: [PATCH 12/17] Fix error of CI --- vm/src/format.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/vm/src/format.rs b/vm/src/format.rs index 9571035b5..c13e3b968 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -369,16 +369,22 @@ impl FormatSpec { Some(FormatType::Number) => { Err("Format code 'n' for object of type 'float' not implemented yet") } - Some(FormatType::GeneralFormatUpper) => Ok(float_ops::format_general( - precision, - magnitude, - float_ops::Case::Upper, - )), - Some(FormatType::GeneralFormatLower) => Ok(float_ops::format_general( - precision, - magnitude, - float_ops::Case::Lower, - )), + Some(FormatType::GeneralFormatUpper) => { + let precision = if precision == 0 { 1 } else { precision }; + Ok(float_ops::format_general( + precision, + magnitude, + float_ops::Case::Upper, + )) + } + Some(FormatType::GeneralFormatLower) => { + let precision = if precision == 0 { 1 } else { precision }; + Ok(float_ops::format_general( + precision, + magnitude, + float_ops::Case::Lower, + )) + } Some(FormatType::ExponentUpper) => Ok(float_ops::format_exponent( precision, magnitude, From 0eba3ba80b947873ef19259b5be67430928dd6a2 Mon Sep 17 00:00:00 2001 From: ChJR Date: Fri, 20 Nov 2020 02:43:56 +0900 Subject: [PATCH 13/17] Prevent stack overflow from format_general which is using precision --- common/src/float_ops.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 8f324a7bf..08acb05d1 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -140,7 +140,11 @@ pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { match magnitude { magnitude if magnitude.is_finite() => { - let r_exp = format!("{:.*e}", precision - 1, magnitude); + let prec = match precision { + 0 => 0, + _ => precision - 1, + }; + let r_exp = format!("{:.*e}", prec, magnitude); let mut parts = r_exp.splitn(2, 'e'); let base = parts.next().unwrap(); let exponent = parts.next().unwrap().parse::().unwrap(); From 3e3bd07ff8aa4346814c2cea6eb3f1a65669e3f6 Mon Sep 17 00:00:00 2001 From: ChJR Date: Fri, 20 Nov 2020 02:46:53 +0900 Subject: [PATCH 14/17] Fix overflow of string reverse iteration This issue does not appear in CI. --- vm/src/builtins/pystr.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vm/src/builtins/pystr.rs b/vm/src/builtins/pystr.rs index 45ebe0100..f19a90666 100644 --- a/vm/src/builtins/pystr.rs +++ b/vm/src/builtins/pystr.rs @@ -180,7 +180,11 @@ impl PyIter for PyStrReverseIterator { break; } } - start.unwrap_or(end - 4) + if end < 4 { + start.unwrap_or(0) + } else { + start.unwrap_or(end - 4) + } }; let stored = zelf.position.swap(start); From 6da4d9714a114cf2fc3b6c0fb4cb84f9002599bb Mon Sep 17 00:00:00 2001 From: ChJR Date: Fri, 20 Nov 2020 03:14:19 +0900 Subject: [PATCH 15/17] Add more tests for general formatting of floating numbers Check for trailing zeros. --- extra_tests/snippets/strings.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extra_tests/snippets/strings.py b/extra_tests/snippets/strings.py index 6fbc64c03..64e3f7eb4 100644 --- a/extra_tests/snippets/strings.py +++ b/extra_tests/snippets/strings.py @@ -298,6 +298,7 @@ assert "%.5g" % 1.020e-13 == '1.02e-13' assert "%.6g" % 1.020e-13 == '1.02e-13' assert "%.7g" % 1.020e-13 == '1.02e-13' assert "%g" % 1.020e-13 == '1.02e-13' +assert "%g" % 1.020e-4 == '0.000102' assert_raises(TypeError, lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25), _msg='format requires a mapping') assert_raises(TypeError, lambda: "My name is %(name)s" % "Foo", _msg='format requires a mapping') @@ -594,6 +595,17 @@ assert '{:G}'.format(float('nan')) == 'NAN' assert '{:g}'.format(float('inf')) == 'inf' assert '{:g}'.format(float('-inf')) == '-inf' assert '{:G}'.format(float('inf')) == 'INF' +assert '{:.0g}'.format(1.020e-13) == '1e-13' +assert '{:.0g}'.format(1.020e-13) == '1e-13' +assert '{:.1g}'.format(1.020e-13) == '1e-13' +assert '{:.2g}'.format(1.020e-13) == '1e-13' +assert '{:.3g}'.format(1.020e-13) == '1.02e-13' +assert '{:.4g}'.format(1.020e-13) == '1.02e-13' +assert '{:.5g}'.format(1.020e-13) == '1.02e-13' +assert '{:.6g}'.format(1.020e-13) == '1.02e-13' +assert '{:.7g}'.format(1.020e-13) == '1.02e-13' +assert '{:g}'.format(1.020e-13) == '1.02e-13' +assert "{:g}".format(1.020e-4) == '0.000102' # remove*fix test def test_removeprefix(): From f124ec8197c6c6130f479a57dc40908914b1adaa Mon Sep 17 00:00:00 2001 From: ChJR Date: Fri, 20 Nov 2020 03:19:28 +0900 Subject: [PATCH 16/17] Fix more trailing zeros in general formatting of floating numbers --- common/src/float_ops.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 08acb05d1..7684f7509 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -137,6 +137,15 @@ pub fn format_exponent(precision: usize, magnitude: f64, case: Case) -> String { } } +fn remove_trailing_zeros(s: String) -> String { + let mut s = s; + while s.ends_with('0') || s.ends_with('.') { + s.truncate(s.len() - 1); + } + + s +} + pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { match magnitude { magnitude if magnitude.is_finite() => { @@ -154,15 +163,12 @@ pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { Case::Upper => 'E', }; - let mut base = format!("{:.*}", precision + 1, base); - while base.ends_with('0') || base.ends_with('.') { - base.truncate(base.len() - 1); - } + let base = remove_trailing_zeros(format!("{:.*}", precision + 1, base)); format!("{}{}{:+#03}", base, e, exponent) } else { let precision = (precision as i64) - 1 - exponent; let precision = precision as usize; - format!("{:.*}", precision, magnitude) + remove_trailing_zeros(format!("{:.*}", precision, magnitude)) } } magnitude if magnitude.is_nan() => format_nan(case), From 3878e8ef4902960bbcd884d69396a144fa51e929 Mon Sep 17 00:00:00 2001 From: ChJR Date: Sun, 22 Nov 2020 03:32:46 +0900 Subject: [PATCH 17/17] Improve code quality --- common/src/float_ops.rs | 6 +----- vm/src/builtins/pystr.rs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs index 7684f7509..582150873 100644 --- a/common/src/float_ops.rs +++ b/common/src/float_ops.rs @@ -149,11 +149,7 @@ fn remove_trailing_zeros(s: String) -> String { pub fn format_general(precision: usize, magnitude: f64, case: Case) -> String { match magnitude { magnitude if magnitude.is_finite() => { - let prec = match precision { - 0 => 0, - _ => precision - 1, - }; - let r_exp = format!("{:.*e}", prec, magnitude); + let r_exp = format!("{:.*e}", precision.saturating_sub(1), magnitude); let mut parts = r_exp.splitn(2, 'e'); let base = parts.next().unwrap(); let exponent = parts.next().unwrap().parse::().unwrap(); diff --git a/vm/src/builtins/pystr.rs b/vm/src/builtins/pystr.rs index f19a90666..3ddf6cb58 100644 --- a/vm/src/builtins/pystr.rs +++ b/vm/src/builtins/pystr.rs @@ -180,11 +180,7 @@ impl PyIter for PyStrReverseIterator { break; } } - if end < 4 { - start.unwrap_or(0) - } else { - start.unwrap_or(end - 4) - } + start.unwrap_or_else(|| end.saturating_sub(4)) }; let stored = zelf.position.swap(start);