Skip to content

'n' support for float format #4865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 15, 2023
2 changes: 0 additions & 2 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,6 @@ def test_format_enum_date(self):
self.assertFormatIsValue('{:%Y %m}', Holiday.IDES_OF_MARCH)
self.assertFormatIsValue('{:%Y %m %M:00}', Holiday.IDES_OF_MARCH)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_enum_float(self):
Konstants = self.Konstants
self.assertFormatIsValue('{}', Konstants.TAU)
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,6 @@ def test_non_ascii(self):
self.assertEqual(format(1+2j, "\u2007^8"), "\u2007(1+2j)\u2007")
self.assertEqual(format(0j, "\u2007^4"), "\u20070j\u2007")

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_locale(self):
try:
oldloc = locale.setlocale(locale.LC_ALL)
Expand Down
25 changes: 15 additions & 10 deletions common/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub enum FormatType {
Character,
Decimal,
Octal,
Number,
Number(Case),
Hex(Case),
Exponent(Case),
GeneralFormat(Case),
Expand All @@ -142,7 +142,8 @@ impl From<&FormatType> for char {
FormatType::Character => 'c',
FormatType::Decimal => 'd',
FormatType::Octal => 'o',
FormatType::Number => 'n',
FormatType::Number(Case::Lower) => 'n',
FormatType::Number(Case::Upper) => 'N',
FormatType::Hex(Case::Lower) => 'x',
FormatType::Hex(Case::Upper) => 'X',
FormatType::Exponent(Case::Lower) => 'e',
Expand All @@ -165,7 +166,8 @@ impl FormatParse for FormatType {
Some('c') => (Some(Self::Character), chars.as_str()),
Some('d') => (Some(Self::Decimal), chars.as_str()),
Some('o') => (Some(Self::Octal), chars.as_str()),
Some('n') => (Some(Self::Number), chars.as_str()),
Some('n') => (Some(Self::Number(Case::Lower)), chars.as_str()),
Some('N') => (Some(Self::Number(Case::Upper)), chars.as_str()),
Some('x') => (Some(Self::Hex(Case::Lower)), chars.as_str()),
Some('X') => (Some(Self::Hex(Case::Upper)), chars.as_str()),
Some('e') => (Some(Self::Exponent(Case::Lower)), chars.as_str()),
Expand Down Expand Up @@ -367,14 +369,14 @@ impl FormatSpec {
| FormatType::Binary
| FormatType::Octal
| FormatType::Hex(_)
| FormatType::Number,
| FormatType::Number(_),
) => {
let ch = char::from(format_type);
Err(FormatSpecError::UnspecifiedFormat(',', ch))
}
(
Some(FormatGrouping::Underscore),
FormatType::String | FormatType::Character | FormatType::Number,
FormatType::String | FormatType::Character | FormatType::Number(_),
) => {
let ch = char::from(format_type);
Err(FormatSpecError::UnspecifiedFormat('_', ch))
Expand All @@ -386,7 +388,7 @@ impl FormatSpec {
fn get_separator_interval(&self) -> usize {
match self.format_type {
Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
Some(FormatType::Decimal | FormatType::Number | FormatType::FixedPoint(_)) => 3,
Some(FormatType::Decimal | FormatType::Number(_) | FormatType::FixedPoint(_)) => 3,
None => 3,
_ => panic!("Separators only valid for numbers!"),
}
Expand Down Expand Up @@ -430,12 +432,12 @@ impl FormatSpec {
| Some(FormatType::Octal)
| Some(FormatType::Hex(_))
| Some(FormatType::String)
| Some(FormatType::Character) => {
| Some(FormatType::Character)
| Some(FormatType::Number(Case::Upper)) => {
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::GeneralFormat(case)) => {
Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
let precision = if precision == 0 { 1 } else { precision };
Ok(float_ops::format_general(
precision,
Expand Down Expand Up @@ -531,7 +533,10 @@ impl FormatSpec {
Ok(result)
}
},
Some(FormatType::Number) => self.format_int_radix(magnitude, 10),
Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
Some(FormatType::Number(Case::Upper)) => {
Err(FormatSpecError::UnknownFormatCode('N', "int"))
}
Some(FormatType::String) => Err(FormatSpecError::UnknownFormatCode('s', "int")),
Some(FormatType::Character) => match (self.sign, self.alternate_form) {
(Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")),
Expand Down
29 changes: 29 additions & 0 deletions extra_tests/snippets/builtin_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,35 @@ def try_mutate_str():
assert '{:g}'.format(1.020e-13) == '1.02e-13'
assert "{:g}".format(1.020e-4) == '0.000102'

# Test n & N formatting
assert '{:n}'.format(999999.1234) == '999999'
assert '{:n}'.format(9999.1234) == '9999.12'
assert '{:n}'.format(-1000000.1234) == '-1e+06'
assert '{:n}'.format(1000000.1234) == '1e+06'
assert '{:.1n}'.format(1000000.1234) == '1e+06'
assert '{:.2n}'.format(1000000.1234) == '1e+06'
assert '{:.3n}'.format(1000000.1234) == '1e+06'
assert '{:.4n}'.format(1000000.1234) == '1e+06'
assert '{:.5n}'.format(1000000.1234) == '1e+06'
assert '{:.6n}'.format(1000000.1234) == '1e+06'
assert '{:.7n}'.format(1000000.1234) == '1000000'
assert '{:.8n}'.format(1000000.1234) == '1000000.1'
assert '{:.10n}'.format(1000000.1234) == '1000000.123'
assert '{:.11n}'.format(1000000.1234) == '1000000.1234'
assert '{:.11n}'.format(-1000000.1234) == '-1000000.1234'
assert '{:0n}'.format(-1000000.1234) == '-1e+06'
assert '{:n}'.format(-1000000.1234) == '-1e+06'
assert '{:-1n}'.format(-1000000.1234) == '-1e+06'

with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
'{:N}'.format(999999.1234)
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
'{:.1N}'.format(1000000.1234)
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
'{:0N}'.format(-1000000.1234)
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
'{:-1N}'.format(-1000000.1234)

# remove*fix test
def test_removeprefix():
s = 'foobarfoo'
Expand Down