Change:
Rust's f32 formatting seems to follow inconsistent rounding rules when rounding to a fixed output precision. Output (): I get this output on Linux x86-64 with both rustc 1.42.0 ( 2020-03-09) and rustc 1.44.0-nightly ( 2020-03-18). I have not tested on other systems, and have not yet tested with f64. Most inputs are rounded following the rule of rounding half to the nearest integer, but 0.5 and -0.5 are rounded away from zero instead. When rounding 0.5 to an integer both 0 or 1 are correct outputs, so this may not be a bug. However it seems strange that the round-half-to-even rule should be followed for 1.5, 2.5, and up while a different rule is followed for 0.5. By comparison, my Python and C versions followed round-half-to-even consistently (rounding 0.5 to 0), though I don't know that they provide any guarantee to follow that rule. Cross-reference: I originally posted about this on the user forum. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
claim
claim
release-assignment
I am not able to find out how Rust is internally handling precision . I check of f32. It looks ok. Can you please guide me?
I think the formatting code starts at . Which is a wrapper around .
claim
release-assignment
claim
I came to this issue also exploring formatting capabilities. For me, it is unclear from the documentation what are the rounding rules. For instance, I cannot explain the following example:
That example looks consistent with round-to-even rules. 2345 is exactly half way between 1.234 and 1.235, so round-to-even selects 1.234 (ends with an even digit). 1. is closer to 1.235 than it is to 1.234 so it must select 1.235. Edit: However, the input numbers may not be exactly representable in binary floating point, which could also affect the results. I haven't checked in this case.
My point here more is that these numbers are not that you would expect to see. For instance, if I implement, e.g., an analogue of Excel I expect to see normal rounding rules applied. However, I am a novice in Rust development, so I do not have a right to impose my opinion to language developers (I do appreciate their work). Still, I think that in the library documentation the fact that round-to-even rules are applied should be mentioned.
Floating point rounding rules are one of those things where no matter what you do, it's unintuitive to someone in some cases. , but it's not surprising that Excel chooses round-to-nearest with 5s going up as its default behavior. , but no Excel user would ever expect that and Excel expressions are more likely to be rounding a single value or only a few values than a large data set. Regardless, should definitely specify the rounding behavior. I simply don't know if Rust is doing round-to-even or round-to-zero or something else.
This also affects : gives
Triage: Hi, are you still working on this issue
No, I didn't get chance. Please go head and fix.
release-assignment
Presumably this is also a problem with rounding in the impl: Why do we see the middle value (2^32 + 4)? Edit: this is just unnecessary rounding during display ():
I saw the same strange behaviour of Display with f64, and made a comparison between Display::fmt and a trivial rounding function. Display was wrong 50% the time in my small test, and was wrong 5% of the time. The code is . Here is the output, with comments where Display is wrong. is wrong with 0.135 but otherwise correct. It's strange that perfectly round values like 0.25 or 0.125, which code well in IEEE-754, are not correctly rounded by of . Of course it depends how the decimal conversion vs rounding is done, for example whether it's converted to BCD or not. But the code is complex and I haven't spent enough time to actually find the part responsible for the rounding.
Since I already had a routine that rounds a number by processing its string representation, I quickly used it to detect erroneously rounded values in a simple test on more values. Maybe not as good as using a crate like but good enough for a quick investigation. I found out the ratio of mismatches was 5 / 22 so about 25%, for different number of fractional digits and only testing values ending by 4 or 5, so 0.4, 0.5 using , 0.14, 0.15 using and so on, since there is little chance to get rounding errors or other digits. That means there is still many numbers in-between that may not round correctly (for ex. 0.4 may round correctly using , but not 0.), but it's quicker to quickly scan many values with different precisions. The ratio remains the same when I check more values by scanning more precisions (same ratio scanning - and scanning - ). The code can be found , if that's any help. EDIT: round-to-even check. If I'm only testing 0.*5 values at all depths, about 50% of the values formatted are not correctly rounded, with round-to-even or round-away-from-zero (the rates are pretty close but not identical)
I'm not sure that matching the output of is the only correct option here. To take an example from C++, is defined to round away from zero (same as Rust's ), while the floating point formatting output depends on the current rounding mode, which on my system defaults to rounding to even. But I think having a consistent rounding mode in the formatter is a desirable property to have, and it looks like Rust's current situation is mostly round-to-even, regardless of what does.
I think I've shown it was not a good solution indeed, since doesn't always produce a predictable result. It would need to provide a correct rounding away from zero and rounding to even.

Discussion:
if order == Ordering::Greater || (order == Ordering::Equal // SAFETY: `buf[len-1]` is initialized. <del> && (len == 0 || unsafe { buf[len - 1].assume_init() } & 1 == 1)) </del> <ins> && len > 0 && unsafe { buf[len - 1].assume_init() } & 1 == 1) </ins> { // if rounding up changes the length, the exponent should also change. // but we've been requested a fixed number of digits, so do not alter the buffer...

assert_eq!("10", format!("{:.0}", 9.9f64)); assert_eq!("9.8", format!("{:.1}", 9.849f64)); assert_eq!("9.9", format!("{:.1}", 9.851f64)); <del> assert_eq!("1", format!("{:.0}", 0.5f64)); </del> <ins> assert_eq!("0", format!("{:.0}", 0.5f64)); </ins> assert_eq!("1.23456789e6", format!("{:e}", 1234567.89f64)); assert_eq!("1.23456789e3", format!("{:e}", 1234.56789f64)); assert_eq!("1.23456789E6", format!("{:E}", 1234567.89f64));

} #[test] <ins> fn test_format_f64_rounds_ties_to_even() { assert_eq!("0", format!("{:.0}", 0.5f64)); assert_eq!("2", format!("{:.0}", 1.5f64)); assert_eq!("2", format!("{:.0}", 2.5f64)); assert_eq!("4", format!("{:.0}", 3.5f64)); assert_eq!("4", format!("{:.0}", 4.5f64)); assert_eq!("6", format!("{:.0}", 5.5f64)); assert_eq!("128", format!("{:.0}", 127.5f64)); assert_eq!("128", format!("{:.0}", 128.5f64)); assert_eq!("0.2", format!("{:.1}", 0.25f64)); assert_eq!("0.8", format!("{:.1}", 0.75f64)); assert_eq!("0.12", format!("{:.2}", 0.125f64)); assert_eq!("0.88", format!("{:.2}", 0.875f64)); assert_eq!("0.062", format!("{:.3}", 0.062f64)); assert_eq!("-0", format!("{:.0}", -0.5f64)); assert_eq!("-2", format!("{:.0}", -1.5f64)); assert_eq!("-2", format!("{:.0}", -2.5f64)); assert_eq!("-4", format!("{:.0}", -3.5f64)); assert_eq!("-4", format!("{:.0}", -4.5f64)); assert_eq!("-6", format!("{:.0}", -5.5f64)); assert_eq!("-128", format!("{:.0}", -127.5f64)); assert_eq!("-128", format!("{:.0}", -128.5f64)); assert_eq!("-0.2", format!("{:.1}", -0.25f64)); assert_eq!("-0.8", format!("{:.1}", -0.75f64)); assert_eq!("-0.12", format!("{:.2}", -0.125f64)); assert_eq!("-0.88", format!("{:.2}", -0.875f64)); assert_eq!("-0.062", format!("{:.3}", -0.062f64)); assert_eq!("2e0", format!("{:.0e}", 1.5f64)); assert_eq!("2e0", format!("{:.0e}", 2.5f64)); assert_eq!("4e0", format!("{:.0e}", 3.5f64)); assert_eq!("4e0", format!("{:.0e}", 4.5f64)); assert_eq!("6e0", format!("{:.0e}", 5.5f64)); assert_eq!("1.28e2", format!("{:.2e}", 127.5f64)); assert_eq!("1.28e2", format!("{:.2e}", 128.5f64)); assert_eq!("-2e0", format!("{:.0e}", -1.5f64)); assert_eq!("-2e0", format!("{:.0e}", -2.5f64)); assert_eq!("-4e0", format!("{:.0e}", -3.5f64)); assert_eq!("-4e0", format!("{:.0e}", -4.5f64)); assert_eq!("-6e0", format!("{:.0e}", -5.5f64)); assert_eq!("-1.28e2", format!("{:.2e}", -127.5f64)); assert_eq!("-1.28e2", format!("{:.2e}", -128.5f64)); assert_eq!("2E0", format!("{:.0E}", 1.5f64)); assert_eq!("2E0", format!("{:.0E}", 2.5f64)); assert_eq!("4E0", format!("{:.0E}", 3.5f64)); assert_eq!("4E0", format!("{:.0E}", 4.5f64)); assert_eq!("6E0", format!("{:.0E}", 5.5f64)); assert_eq!("1.28E2", format!("{:.2E}", 127.5f64)); assert_eq!("1.28E2", format!("{:.2E}", 128.5f64)); assert_eq!("-2E0", format!("{:.0E}", -1.5f64)); assert_eq!("-2E0", format!("{:.0E}", -2.5f64)); assert_eq!("-4E0", format!("{:.0E}", -3.5f64)); assert_eq!("-4E0", format!("{:.0E}", -4.5f64)); assert_eq!("-6E0", format!("{:.0E}", -5.5f64)); assert_eq!("-1.28E2", format!("{:.2E}", -127.5f64)); assert_eq!("-1.28E2", format!("{:.2E}", -128.5f64)); } #[test] </ins> fn test_format_f32() { assert_eq!("1", format!("{:.0}", 1.0f32)); assert_eq!("9", format!("{:.0}", 9.4f32)); assert_eq!("10", format!("{:.0}", 9.9f32)); assert_eq!("9.8", format!("{:.1}", 9.849f32)); assert_eq!("9.9", format!("{:.1}", 9.851f32)); <del> assert_eq!("1", format!("{:.0}", 0.5f32)); </del> <ins> assert_eq!("0", format!("{:.0}", 0.5f32)); </ins> assert_eq!("1.2345679e6", format!("{:e}", 1234567.89f32)); assert_eq!("1.2345679e3", format!("{:e}", 1234.56789f32)); assert_eq!("1.2345679E6", format!("{:E}", 1234567.89f32));

assert_eq!("1234.6", format!("{:.1?}", 1234.56789f32)); } <ins> #[test] fn test_format_f32_rounds_ties_to_even() { assert_eq!("0", format!("{:.0}", 0.5f32)); assert_eq!("2", format!("{:.0}", 1.5f32)); assert_eq!("2", format!("{:.0}", 2.5f32)); assert_eq!("4", format!("{:.0}", 3.5f32)); assert_eq!("4", format!("{:.0}", 4.5f32)); assert_eq!("6", format!("{:.0}", 5.5f32)); assert_eq!("128", format!("{:.0}", 127.5f32)); assert_eq!("128", format!("{:.0}", 128.5f32)); assert_eq!("0.2", format!("{:.1}", 0.25f32)); assert_eq!("0.8", format!("{:.1}", 0.75f32)); assert_eq!("0.12", format!("{:.2}", 0.125f32)); assert_eq!("0.88", format!("{:.2}", 0.875f32)); assert_eq!("0.062", format!("{:.3}", 0.062f32)); assert_eq!("-0", format!("{:.0}", -0.5f32)); assert_eq!("-2", format!("{:.0}", -1.5f32)); assert_eq!("-2", format!("{:.0}", -2.5f32)); assert_eq!("-4", format!("{:.0}", -3.5f32)); assert_eq!("-4", format!("{:.0}", -4.5f32)); assert_eq!("-6", format!("{:.0}", -5.5f32)); assert_eq!("-128", format!("{:.0}", -127.5f32)); assert_eq!("-128", format!("{:.0}", -128.5f32)); assert_eq!("-0.2", format!("{:.1}", -0.25f32)); assert_eq!("-0.8", format!("{:.1}", -0.75f32)); assert_eq!("-0.12", format!("{:.2}", -0.125f32)); assert_eq!("-0.88", format!("{:.2}", -0.875f32)); assert_eq!("-0.062", format!("{:.3}", -0.062f32)); assert_eq!("2e0", format!("{:.0e}", 1.5f32)); assert_eq!("2e0", format!("{:.0e}", 2.5f32)); assert_eq!("4e0", format!("{:.0e}", 3.5f32)); assert_eq!("4e0", format!("{:.0e}", 4.5f32)); assert_eq!("6e0", format!("{:.0e}", 5.5f32)); assert_eq!("1.28e2", format!("{:.2e}", 127.5f32)); assert_eq!("1.28e2", format!("{:.2e}", 128.5f32)); assert_eq!("-2e0", format!("{:.0e}", -1.5f32)); assert_eq!("-2e0", format!("{:.0e}", -2.5f32)); assert_eq!("-4e0", format!("{:.0e}", -3.5f32)); assert_eq!("-4e0", format!("{:.0e}", -4.5f32)); assert_eq!("-6e0", format!("{:.0e}", -5.5f32)); assert_eq!("-1.28e2", format!("{:.2e}", -127.5f32)); assert_eq!("-1.28e2", format!("{:.2e}", -128.5f32)); assert_eq!("2E0", format!("{:.0E}", 1.5f32)); assert_eq!("2E0", format!("{:.0E}", 2.5f32)); assert_eq!("4E0", format!("{:.0E}", 3.5f32)); assert_eq!("4E0", format!("{:.0E}", 4.5f32)); assert_eq!("6E0", format!("{:.0E}", 5.5f32)); assert_eq!("1.28E2", format!("{:.2E}", 127.5f32)); assert_eq!("1.28E2", format!("{:.2E}", 128.5f32)); assert_eq!("-2E0", format!("{:.0E}", -1.5f32)); assert_eq!("-2E0", format!("{:.0E}", -2.5f32)); assert_eq!("-4E0", format!("{:.0E}", -3.5f32)); assert_eq!("-4E0", format!("{:.0E}", -4.5f32)); assert_eq!("-6E0", format!("{:.0E}", -5.5f32)); assert_eq!("-1.28E2", format!("{:.2E}", -127.5f32)); assert_eq!("-1.28E2", format!("{:.2E}", -128.5f32)); } </ins> fn is_exponential(s: &str) -> bool { s.contains("e") || s.contains("E") }

// check exact rounding for zero- and negative-width cases let start; <del> if expected[0] >= b'5' { </del> <ins> if expected[0] > b'5' { </ins> try_fixed!(f(&decoded) => &mut buf, expectedk, b"1", expectedk + 1; "zero-width rounding-up mismatch for v={v}:  actual {actual:?}, expected {expected:?}",

assert_eq!(to_string(f, 999.5, Minus, 3), "999.500"); assert_eq!(to_string(f, 999.5, Minus, 30), "999.500000000000000000000000000000"); <del> assert_eq!(to_string(f, 0.5, Minus, 0), "1"); </del> <ins> assert_eq!(to_string(f, 0.5, Minus, 0), "0"); </ins> assert_eq!(to_string(f, 0.5, Minus, 1), "0.5"); assert_eq!(to_string(f, 0.5, Minus, 2), "0.50"); assert_eq!(to_string(f, 0.5, Minus, 3), "0.500");