8000 Merge pull request #1651 from doyshinda/impl_format_for_float · RustPython/RustPython@31d3fc6 · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit 31d3fc6

Browse files
authored
Merge pull request #1651 from doyshinda/impl_format_for_float
Add support for string formatting of floats with ":f" format code
2 parents deee1f7 + 30473d3 commit 31d3fc6

File tree

4 files changed

+142
-13
lines changed

4 files changed

+142
-13
lines changed

parser/src/fstring.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl<'a> FStringParser<'a> {
8383
}))
8484
} else {
8585
spec = Some(Box::new(Constant {
86-
value: spec_expression.trim().to_string(),
86+
value: spec_expression.to_string(),
8787
}))
8888
}
8989
}

tests/snippets/strings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,36 @@ def try_mutate_str():
398398
# >>> '{{literal}}'.format_map('foo')
399399
# '{literal}'
400400
assert '{{literal}}'.format_map('foo') == '{literal}'
401+
402+
# test formatting float values
403+
assert f'{5:f}' == '5.000000'
404+
assert f'{-5:f}' == '-5.000000'
405+
assert f'{5.0:f}' == '5.000000'
406+
assert f'{-5.0:f}' == '-5.000000'
407+
assert f'{5:.2f}' == '5.00'
408+
assert f'{5.0:.2f}' == '5.00'
409+
assert f'{-5:.2f}' == '-5.00'
410+
assert f'{-5.0:.2f}' == '-5.00'
411+
assert f'{5.0:04f}' == '5.000000'
412+
assert f'{5.1234:+f}' == '+5.123400'
413+
assert f'{5.1234: f}' == ' 5.123400'
414+
assert f'{5.1234:-f}' == '5.123400'
415+
assert f'{-5.1234:-f}' == '-5.123400'
416+
assert f'{1.0:+}' == '+1.0'
417+
assert f'--{1.0:f>4}--' == '--f1.0--'
418+
assert f'--{1.0:f<4}--' == '--1.0f--'
419+
assert f'--{1.0:d^4}--' == '--1.0d--'
420+
assert f'--{1.0:d^5}--' == '--d1.0d--'
421+
assert f'--{1.1:f>6}--' == '--fff1.1--'
422+
assert '{}'.format(float('nan')) == 'nan'
423+
assert '{:f}'.format(float('nan')) == 'nan'
424+
assert '{:f}'.format(float('-nan')) == 'nan'
425+
assert '{:F}'.format(float('nan')) == 'NAN'
426+
assert '{}'.format(float('inf')) == 'inf'
427+
assert '{:f}'.format(float('inf')) == 'inf'
428+
assert '{:f}'.format(float('-inf')) == '-inf'
429+
assert '{:F}'.format(float('inf')) == 'INF'
430+
assert f'{1234567890.1234:,.2f}' == '1,234,567,890.12'
431+
assert f'{1234567890.1234:_.2f}' == '1_234_567_890.12'
432+
with AssertRaises(ValueError, msg="Unknown format code 'd' for object of type 'float'"):
433+
f'{5.0:04d}'

vm/src/format.rs

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use num_bigint::{BigInt, Sign};
2+
use num_traits::cast::ToPrimitive;
23
use num_traits::Signed;
34
use std::cmp;
45
use std::str::FromStr;
@@ -281,14 +282,22 @@ impl FormatSpec {
281282
separator: char,
282283
) -> String {
283284
let mut result = String::new();
284-
let mut remaining: usize = magnitude_string.len();
285-
for c in magnitude_string.chars() {
285+
286+
// Don't add separators to the floating decimal point of numbers
287+
let mut parts = magnitude_string.splitn(2, '.');
288+
let magnitude_integer_string = parts.next().unwrap();
289+
let mut remaining: usize = magnitude_integer_string.len();
290+
for c in magnitude_integer_string.chars() {
286291
result.push(c);
287292
remaining -= 1;
288293
if remaining % interval == 0 && remaining > 0 {
289294
result.push(separator);
290295
}
291296
}
297+
if let Some(part) = parts.next() {
298+
result.push('.');
299+
result.push_str(part);
300+
}
292301
result
293302
}
294303

@@ -300,6 +309,7 @@ impl FormatSpec {
300309
Some(FormatType::HexLower) => 4,
301310
Some(FormatType::HexUpper) => 4,
302311
Some(FormatType::Number) => 3,
312+
Some(FormatType::FixedPointLower) | Some(FormatType::FixedPointUpper) => 3,
303313
None => 3,
304314
_ => panic!("Separators only valid for numbers!"),
305315
}
@@ -321,8 +331,75 @@ impl FormatSpec {
321331
}
322332
}
323333

334+
pub fn format_float(&self, num: f64) -> Result<String, &'static str> {
335+
let precision = self.precision.unwrap_or(6);
336+
let magnitude = num.abs();
337+
let raw_magnitude_string_result: Result<String, &'static str> = match self.format_type {
338+
Some(FormatType::FixedPointUpper) => match magnitude {
339+
magnitude if magnitude.is_nan() => Ok("NAN".to_string()),
340+
magnitude if magnitude.is_infinite() => Ok("INF".to_string()),
341+
_ => Ok(format!("{:.*}", precision, magnitude)),
342+
},
343+
Some(FormatType::FixedPointLower) => match magnitude {
344+
magnitude if magnitude.is_nan() => Ok("nan".to_string()),
345+
magnitude if magnitude.is_infinite() => Ok("inf".to_string()),
346+
_ => Ok(format!("{:.*}", precision, magnitude)),
347+
},
348+
Some(FormatType::Decimal) => Err("Unknown format code 'd' for object of type 'float'"),
349+
Some(FormatType::Binary) => Err("Unknown format code 'b' for object of type 'float'"),
350+
Some(FormatType::Octal) => Err("Unknown format code 'o' for object of type 'float'"),
351+
Some(FormatType::HexLower) => Err("Unknown format code 'x' for object of type 'float'"),
352+
Some(FormatType::HexUpper) => Err("Unknown format code 'X' for object of type 'float'"),
353+
Some(FormatType::String) => Err("Unknown format code 's' for object of type 'float'"),
354+
Some(FormatType::Character) => {
355+
Err("Unknown format code 'c' for object of type 'float'")
356+
}
357+
Some(FormatType::Number) => {
358+
Err("Format code 'n' for object of type 'float' not implemented yet")
359+
}
360+
Some(FormatType::GeneralFormatUpper) => {
361+
Err("Format code 'G' for object of type 'float' not implemented yet")
362+
}
363+
Some(FormatType::GeneralFormatLower) => {
364+
Err("Format code 'g' for object of type 'float' not implemented yet")
365+
}
366+
Some(FormatType::ExponentUpper) => {
367+
Err("Format code 'E' for object of type 'float' not implemented yet")
368+
}
369+
Some(FormatType::ExponentLower) => {
370+
Err("Format code 'e' for object of type 'float' not implemented yet")
371+
}
372+
None => {
373+
match magnitude {
374+
magnitude if magnitude.is_nan() => Ok("nan".to_string()),
375+
magnitude if magnitude.is_infinite() => Ok("inf".to_string()),
376+
// Using the Debug format here to prevent the automatic conversion of floats
377+
// ending in .0 to their integer representation (e.g., 1.0 -> 1)
378+
_ => Ok(format!("{:?}", magnitude)),
379+
}
380+
}
381+
};
382+
383+
if raw_magnitude_string_result.is_err() {
384+
return raw_magnitude_string_result;
385+
}
386+
387+
let magnitude_string = self.add_magnitude_separators(raw_magnitude_string_result.unwrap());
388+
let format_sign = self.sign.unwrap_or(FormatSign::Minus);
389+
let sign_str = if num.is_sign_negative() && !num.is_nan() {
390+
"-"
391+
} else {
392+
match format_sign {
393+
FormatSign::Plus => "+",
394+
FormatSign::Minus => "",
395+
FormatSign::MinusOrSpace => " ",
396+
}
397+
};
398+
399+
self.format_sign_and_align(magnitude_string, sign_str)
400+
}
401+
324402
pub fn format_int(&self, num: &BigInt) -> Result<String, &'static str> {
325-
let fill_char = self.fill.unwrap_or(' ');
326403
let magnitude = num.abs();
327404
let prefix = if self.alternate_form {
328405
match self.format_type {
@@ -360,11 +437,11 @@ impl FormatSpec {
360437
Some(FormatType::ExponentLower) => {
361438
Err("Unknown format code 'e' for object of type 'int'")
362439
}
363-
Some(FormatType::FixedPointUpper) => {
364-
Err("Unknown format code 'F' for object of type 'int'")
365-
}
366-
Some(FormatType::FixedPointLower) => {
367-
Err("Unknown format code 'f' for object of type 'int'")
440+
Some(FormatType::FixedPointUpper) | Some(FormatType::FixedPointLower) => {
441+
match num.to_f64() {
442+
Some(float) => return self.format_float(float),
443+
_ => Err("Unable to convert int to float"),
444+
}
368445
}
369446
None => Ok(magnitude.to_str_radix(10)),
370447
};
@@ -376,10 +453,6 @@ impl FormatSpec {
376453
prefix,
377454
self.add_magnitude_separators(raw_magnitude_string_result.unwrap())
378455
);
379-
let align = self.align.unwrap_or(FormatAlign::Right);
380-
381-
// Use the byte length as the string length since we're in ascii
382-
let num_chars = magnitude_string.len();
383456

384457
let format_sign = self.sign.unwrap_or(FormatSign::Minus);
385458
let sign_str = match num.sign() {
@@ -391,6 +464,19 @@ impl FormatSpec {
391464
},
392465
};
393466

467+
self.format_sign_and_align(magnitude_string, sign_str)
468+
}
469+
470+
fn format_sign_and_align(
471+
&self,
472+
magnitude_string: String,
473+
sign_str: &str,
474+
) -> Result<String, &'static str> {
475+
let align = self.align.unwrap_or(FormatAlign::Right);
476+
477+
// Use the byte length as the string length since we're in ascii
478+
let num_chars = magnitude_string.len();
479+
let fill_char = self.fill.unwrap_or(' ');
394480
let fill_chars_needed: i32 = self.width.map_or(0, |w| {
395481
cmp::max(0, (w as i32) - (num_chars as i32) - (sign_str.len() as i32))
396482
});

vm/src/obj/objfloat.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::objint::{self, PyInt, PyIntRef};
88
use super::objstr::{PyString, PyStringRef};
99
use super::objtype::{self, PyClassRef};
1010
use crate::exceptions::PyBaseExceptionRef;
11+
use crate::format::FormatSpec;
1112
use crate::function::{OptionalArg, OptionalOption};
1213
use crate::pyhash;
1314
use crate::pyobject::{
@@ -210,6 +211,15 @@ impl PyFloat {
210211
vm.ctx.new_bool(result)
211212
}
212213

214+
#[pymethod(name = "__format__")]
215+
fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult<String> {
216+
let format_spec = FormatSpec::parse(spec.as_str());
217+
match format_spec.format_float(self.value) {
218+
Ok(string) => Ok(string),
219+
Err(err) => Err(vm.new_value_error(err.to_string())),
220+
}
221+
}
222+
213223
#[pymethod(name = "__eq__")]
214224
fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
215225
self.cmp(other, |a, b| a == b, |a, b| int_eq(a, b), vm)

0 commit comments

Comments
 (0)
0