Skip to content
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

document that std::fmt uses ROUND_HALF_EVEN #112742

Closed
Krappa322 opened this issue Jun 17, 2023 · 13 comments
Closed

document that std::fmt uses ROUND_HALF_EVEN #112742

Krappa322 opened this issue Jun 17, 2023 · 13 comments
Assignees
Labels
A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools A-floating-point Area: Floating point numbers and arithmetic T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@Krappa322
Copy link
Contributor

Krappa322 commented Jun 17, 2023

I tried this code:

    println!("{:.0}", 10.5f64);

I expected to see this happen: It prints 11

Instead, this happened: It prints 10

As far as I am able to find 10.5 has a perfect (lossless) representation in f64-space so it does not seem to be because of floating point error to me.

If I put in 9.5 instead it does work as expected (it prints 10), which is weird because that also has a perfect representation as f64. Is it applying a round_ties_even() kind of logic? If it is then I would expect this to be documented e.g. under https://doc.rust-lang.org/std/fmt/#precision

Meta

I've been using rust playground, rust version 1.70.0

@Krappa322 Krappa322 added the C-bug Category: This is a bug. label Jun 17, 2023
@zredb
Copy link
Contributor

zredb commented Jun 17, 2023

I tested it on Playground and it is indeed related to Odd and Even.

fn main() {
    println!("{:.0}", 12.5f64);
    println!("{:.0}", 11.5f64);
    println!("{:.0}", 10.5f64);
    println!("{:.0}", 9.5f64);
    println!("{:.0}", 8.5f64);
    println!("{:.0}", 7.5f64);
}

this will print:

12
12
10
10
8
8

@eval-exec
Copy link
Contributor

@rustbot claim

@quixoticaxis
Copy link

Is it a bug though? Ties to even seems to be the recommended default of IEEE754 for floating point.

@quixoticaxis
Copy link

As a side-note: IMHO, duplicating the description of floating point math to the documentation of Rust formatting facilities looks like an overkill as there are numerous quircks.

@Krappa322
Copy link
Contributor Author

printf seems to do the same thing as well:

    long double num = 10.5;
    printf("%.0llf %.0llf\n", num, round(num));

outputs "10 11" in godbolt with both gcc and clang. Could be dependent on the libc implementation though of course. I can't find anything in C documentation that mentions the rounding strategy either

@Krappa322
Copy link
Contributor Author

In any case I would say it's quite unexpected both for rust and for c that calling round() produces a different result than printing the number with a precision of zero. It really does seem worth mentioning in documentation

@Krappa322
Copy link
Contributor Author

Looks like someone has already done the research for us and found out that it's inconsistent in C and in tons of other languages as well, not only between languages but also between platforms using the same language. For example, it says that MSVC does rounding away from zero
https://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/

(see also https://stackoverflow.com/questions/10357192/printf-rounding-behavior-for-doubles - that's how I found that article)

@Krappa322
Copy link
Contributor Author

@eval-exec
Copy link
Contributor

eval-exec commented Jun 18, 2023

I found this:

    println!("{:.1}", 3.33f64); // 3.3
    println!("{:.1}", -3.33f64); // -3.3
    println!("{:.1}", -3.55f64); // -3.5
    println!("{:.1}", 4.54f64); // 4.5
    println!("{:.1}", 4.55f64); // 4.5
    println!("{:.1}", 4.56f64); // 4.6
    println!("{:.1}", -4.54f64); // -4.5
    println!("{:.1}", -4.55f64); // -4.5
    println!("{:.1}", -4.56f64); // -4.6

    println!("{:.1}", -5.74f64); // -5.7
    println!("{:.1}", -5.75f64); // -5.8
    println!("{:.1}", -5.76f64); // -5.8

I'm confused, I think println!("{:.1}", 4.55f64); should get 4.6, but it get 4.5. 🤔

@eval-exec
Copy link
Contributor

eval-exec commented Jun 18, 2023

I'm investigating the source code of this:

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));
}

@asquared31415
Copy link
Contributor

4.55f64 cannot be represented exactly, and is actually 4.54999999999999982236431605997
on the other hand .5, .75, and .125 can be represented exactly at these magnitudes, since those are reciprocals of powers of 2, and these numbers are rather small.

@jyn514 jyn514 added A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools A-floating-point Area: Floating point numbers and arithmetic T-libs Relevant to the library team, which will review and decide on the PR/issue. and removed C-bug Category: This is a bug. labels Jun 19, 2023
@jyn514 jyn514 changed the title Float rounding in std::fmt is incorrect when a fraction is exactly .5 document that std::fmt uses ROUND_HALF_EVEN Jun 19, 2023
@HTGAzureX1212
Copy link
Contributor

@rustbot claim

@ehuss
Copy link
Contributor

ehuss commented Sep 27, 2024

I'm going to close as I believe this is resolved by #120967. Please let us know if this should be kept open.

@ehuss ehuss closed this as completed Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools A-floating-point Area: Floating point numbers and arithmetic T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants