Auto merge of #102935 - ajtribick:display-float-0.5-fixed-0, r=scottmcm

Fix inconsistent rounding of 0.5 when formatted to 0 decimal places

As described in #70336, when displaying values to zero decimal places the value of 0.5 is rounded to 1, which is inconsistent with the display of other half-integer values which round to even.

From testing the flt2dec implementation, it looks like this comes down to the condition in the fixed-width Dragon implementation where an empty buffer is treated as a case to apply rounding up. I believe the change below fixes it and updates only the relevant tests.

Nevertheless I am aware this is very much a core piece of functionality, so please take a very careful look to make sure I haven't missed anything. I hope this change does not break anything in the wider ecosystem as having a consistent rounding behaviour in floating point formatting is in my opinion a useful feature to have.

Resolves #70336
This commit is contained in:
bors 2022-11-16 07:20:30 +00:00
commit e702534763
3 changed files with 125 additions and 5 deletions

View File

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

View File

@ -5,7 +5,7 @@ fn test_format_f64() {
assert_eq!("10", format!("{:.0}", 9.9f64)); assert_eq!("10", format!("{:.0}", 9.9f64));
assert_eq!("9.8", format!("{:.1}", 9.849f64)); assert_eq!("9.8", format!("{:.1}", 9.849f64));
assert_eq!("9.9", format!("{:.1}", 9.851f64)); assert_eq!("9.9", format!("{:.1}", 9.851f64));
assert_eq!("1", format!("{:.0}", 0.5f64)); assert_eq!("0", format!("{:.0}", 0.5f64));
assert_eq!("1.23456789e6", format!("{:e}", 1234567.89f64)); assert_eq!("1.23456789e6", format!("{:e}", 1234567.89f64));
assert_eq!("1.23456789e3", format!("{:e}", 1234.56789f64)); assert_eq!("1.23456789e3", format!("{:e}", 1234.56789f64));
assert_eq!("1.23456789E6", format!("{:E}", 1234567.89f64)); assert_eq!("1.23456789E6", format!("{:E}", 1234567.89f64));
@ -24,6 +24,66 @@ fn test_format_f64() {
assert_eq!("1234.6", format!("{:.1?}", 1234.56789f64)); assert_eq!("1234.6", format!("{:.1?}", 1234.56789f64));
} }
#[test]
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] #[test]
fn test_format_f32() { fn test_format_f32() {
assert_eq!("1", format!("{:.0}", 1.0f32)); assert_eq!("1", format!("{:.0}", 1.0f32));
@ -31,7 +91,7 @@ fn test_format_f32() {
assert_eq!("10", format!("{:.0}", 9.9f32)); assert_eq!("10", format!("{:.0}", 9.9f32));
assert_eq!("9.8", format!("{:.1}", 9.849f32)); assert_eq!("9.8", format!("{:.1}", 9.849f32));
assert_eq!("9.9", format!("{:.1}", 9.851f32)); assert_eq!("9.9", format!("{:.1}", 9.851f32));
assert_eq!("1", format!("{:.0}", 0.5f32)); assert_eq!("0", format!("{:.0}", 0.5f32));
assert_eq!("1.2345679e6", format!("{:e}", 1234567.89f32)); assert_eq!("1.2345679e6", format!("{:e}", 1234567.89f32));
assert_eq!("1.2345679e3", format!("{:e}", 1234.56789f32)); assert_eq!("1.2345679e3", format!("{:e}", 1234.56789f32));
assert_eq!("1.2345679E6", format!("{:E}", 1234567.89f32)); assert_eq!("1.2345679E6", format!("{:E}", 1234567.89f32));
@ -50,6 +110,66 @@ fn test_format_f32() {
assert_eq!("1234.6", format!("{:.1?}", 1234.56789f32)); assert_eq!("1234.6", format!("{:.1?}", 1234.56789f32));
} }
#[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));
}
fn is_exponential(s: &str) -> bool { fn is_exponential(s: &str) -> bool {
s.contains("e") || s.contains("E") s.contains("e") || s.contains("E")
} }

View File

@ -138,7 +138,7 @@ fn check_exact<F, T>(mut f: F, v: T, vstr: &str, expected: &[u8], expectedk: i16
// check exact rounding for zero- and negative-width cases // check exact rounding for zero- and negative-width cases
let start; let start;
if expected[0] >= b'5' { if expected[0] > b'5' {
try_fixed!(f(&decoded) => &mut buf, expectedk, b"1", expectedk + 1; try_fixed!(f(&decoded) => &mut buf, expectedk, b"1", expectedk + 1;
"zero-width rounding-up mismatch for v={v}: \ "zero-width rounding-up mismatch for v={v}: \
actual {actual:?}, expected {expected:?}", actual {actual:?}, expected {expected:?}",
@ -1007,7 +1007,7 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
assert_eq!(to_string(f, 999.5, Minus, 3), "999.500"); assert_eq!(to_string(f, 999.5, Minus, 3), "999.500");
assert_eq!(to_string(f, 999.5, Minus, 30), "999.500000000000000000000000000000"); assert_eq!(to_string(f, 999.5, Minus, 30), "999.500000000000000000000000000000");
assert_eq!(to_string(f, 0.5, Minus, 0), "1"); assert_eq!(to_string(f, 0.5, Minus, 0), "0");
assert_eq!(to_string(f, 0.5, Minus, 1), "0.5"); 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, 2), "0.50");
assert_eq!(to_string(f, 0.5, Minus, 3), "0.500"); assert_eq!(to_string(f, 0.5, Minus, 3), "0.500");