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

Conversion from u32 to f32 via cast() can be inexact #5

Closed
BartMassey opened this issue Mar 18, 2021 · 5 comments · Fixed by #6
Closed

Conversion from u32 to f32 via cast() can be inexact #5

BartMassey opened this issue Mar 18, 2021 · 5 comments · Fixed by #6

Comments

@BartMassey
Copy link
Contributor

My understanding from the docs is that conversions via cast() should always be exact, but this is not currently the case.

let x: f32 = (!0u32).cast();
println!("{}", !0u32); // prints 4294967295
println!("{}", x); // prints 4294967300
@dhardy
Copy link
Contributor

dhardy commented Mar 19, 2021

There is a check ...

macro_rules! impl_via_as_revert_check {
    ($x:ty: $y:ty) => {
        impl Conv<$x> for $y {
            #[inline]
            fn conv(x: $x) -> $y {
                let y = x as $y;
                debug_assert_eq!(x, y as $x);
                y
            }
        }
    };
}

impl_via_as_revert_check!(u32: f32);

... but apparently it doesn't work:

fn main() {
    println!("{}, {}, {}", !0u32, !0u32 as f32, (!0u32 as f32) as u32);
}

4294967295, 4294967300, 4294967295

Thanks for reporting.

@dhardy
Copy link
Contributor

dhardy commented Mar 19, 2021

Interestingly, (!0u32 as f32) as u64 is 4294967296 (=2**32) despite (!0u32 as f32) printing as 4294967300, and there shouldn't be any futher rounding happening here — so presumably the std::fmt::Display impl of f32 is wrong. Presumably this is rust-lang/rust#70336.

@BartMassey
Copy link
Contributor Author

OK, so I think I've figured this one out. Ugh.

Rust recently defined their cast semantics: casting a float to an int now sets it to maxint if the result is too large to fit. This is IMHO a terrible, terrible design decision, but there we are.

So easy_cast::cast()'s check that a number is properly cast back to its original value works for every u32/f32 case except !0u32. Ugh. You should see the same problem for the max value of every integer type and the min value of every signed type.

Why don't Rust's casts just fail when they aren't exact? Wouldn't that be nice?

use easy_cast::Cast;

fn main() {
    let x = !0u32 - 1;
    let y = x as f32;
    println!("{}", x); // prints 429467294
    println!("{}", y); // prints 429467300
    println!("{}", y as u32); // prints 429467295 (neat?)

    let xx = !0u32;
    let y: f32 = x.cast(); // does not fail as it should
    println!("{}", xx); // prints 429467295
    println!("{}", y); // prints 429467300
    println!("{}", y as u32); // prints 429467295 (neat?)
    
    let _: f32 = x.cast(); // fails as desired
}

@BartMassey
Copy link
Contributor Author

Looks like you already caught this and fixed it in PR #6. Nice!

@dhardy
Copy link
Contributor

dhardy commented Mar 20, 2021

Why don't Rust's casts just fail when they aren't exact? Wouldn't that be nice?

Because as was defined quite early and not fixed before 1.0. It does several jobs, e.g. u8::MAX as i8 as i32 as u32 == u32::MAX.

I think most of the uses of this "revert check" were wrong actually hence why I replaced them. Only the f64 → f32 → f64 check is okay since f32 as f64 is always exact.

@dhardy dhardy closed this as completed in #6 Mar 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants