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

[question] Is num_traits::cast::<f64, f32>(f64) -> Option<f32> just f64 as f32? So it never returns None. #266

Closed
viruscamp opened this issue Apr 7, 2023 · 4 comments

Comments

@viruscamp
Copy link

Is num_traits::cast::<f64, f32>(f64) -> Option<f32> just f64 as f32? So it never returns None.
But meanwhile cast::<u64, u32>(u64::MAX) return None.
I checked the doc, found there were 'conversions with possible precision loss or truncation are admitted, ..., or even a large f64 saturating to f32 infinity.' in NumCast. So it's as design? It's should be emphasized with large and red fonts, and also in cast.

use num_traits::cast; // 0.2.15
fn main() {
    dbg!(f64::MAX as f32); // inf
    dbg!(cast::<f64, f32>(f64::MAX)); // inf
    dbg!(cast::<u64, u32>(u64::MAX)); // None
}

Is there any reasonable cast_f64_to_f32() which will fail at something like cast_f64_to_f32(f32::MAX as f64 + 1.0f64) == None.

@viruscamp
Copy link
Author

Ah, I found fn cast::f32<f64>(x: f64) -> Result<f32, _> in crate cast is ok.

@cuviper
Copy link
Member

cuviper commented Apr 7, 2023

There are varying opinions on the matter. The main difference with integers is that those have no representation of "overflow" at all, whereas any float operation that overflows will get infinity. It's probably a bad choice of words to call that "saturating" though. Some people don't even want float casting to admit any loss, and this debate is a big reason why floats don't implement TryFrom yet.

@viruscamp
Copy link
Author

The std lib only has f64 as f32 that never fail. And it does not have impl TryFrom<f64> for f32. It is inconvenient but clear.

num_traits::cast::<f64, f32>(f64) -> Option<f32> looks all right, but never return None as expected, it's confusing.

@cuviper
Copy link
Member

cuviper commented Apr 7, 2023

Well, to answer your original question directly:

Is num_traits::cast::<f64, f32>(f64) -> Option<f32> just f64 as f32? So it never returns None.

Yes. Once you accept lossy conversions, as we have here, then all f64 values have a mapping to f32, so that never returns None. The API still uses an Option because it is generic with types where this may not be the case. Some as conversions even used to have Undefined Behavior -- like float to int overflow or NaN (which now saturates and maps NaN to 0) -- and num-traits returns None for those.

The cast crate made a different choice, which is fine -- they still allow lossy conversions but return an error on large finite values. They still allow f64::INFINITY to cast to f32::INFINITY though, which makes it a little weird in my opinion to have a "gap" in convertible numbers, but if that suits you then go ahead and use it!

Note, the cast crate also errors for values over MAX at all -- e.g. cast::f32::<f64>(3.40282347e38) returns Err(Overflow), while as and num-traits will truncate to f32::MAX1. It also errors for cast::u8::<f64>(255.1), which works fine with truncating as, even before the saturating float-to-int change. Again, that may or may not be the behavior you actually want.

Footnotes

  1. f32::MAX is even defined in source with a similar literal, 3.40282347e+38_f32, but the raw value is precisely 340282346638528859811704183484516925440.0. In f64, there are more bits to get closer to ...47...

@cuviper cuviper closed this as completed Apr 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants