[Pre-ACP] `TryFrom` for exact float-int conversions

As far as I can tell, the standard library doesn't have a method to convert between floats and ints with an error if the value can't be expressed exactly (like the behavior for converting between different-size ints), so I'd like to use TryFrom to fill that niche.

Specifically, for any pair of integer and floating-point type, there will be impl TryFrom<int> for float (minus the ones which already exist from the blanket impl from infallible From, like f64: From<i32>), and impl TryFrom<float> for int which semantically work by imagining the value as an arbitrary real number and only succeeding if the destination type has an exact match in the destination type (and if casting 0 to a float, pick positive zero).

Specifically, they'd have these useful properties:

  • If a .try_into()? call succeeds, the returned value will match the value produced by an as cast.
  • Any chain of .try_into()? calls which ends at the same type, if they both succeed, produce values which compare equal.
    • The empty chain is counted, so for value: T, T::try_from(U::try_from(value)?)? == value will either return an error if value isn't exactly expressible as U or evaluate to true.
    • -0.0 == 0.0 is a true comparison despite the distinct values, so u32::try_from(-0.0)? == 0 is allowed to succeed.
    • Float values like NaN and the infinities will always fail to convert to any integer.
  • For types T and U where T: From<U> (e.g. u64 and u32), if T::try_from(value)? succeeds, then U::try_from(value)? will also succeed for that same input.
  • No rounding is automatically done, so e.g. u32::try_from(12.0001) will always fail, but users can easily chain with a call to .round(), .trunc(), or whatever other rounding operations they want.

If you want saturating and any rounding scheme, the existing methods and as casting works well. This TryFrom implementation will provide an option for no saturating nor rounding, and the existing rounding methods complement it by allowing rounding also. The only combination I can think of that isn't enabled by this conversion is if you want to saturate but not round (e.g. allow 300. to 255u8, but disallow 12.0001 to 12u8), which I have never needed.

What do y'all think? Would anyone else find this useful?

1 Like

Prior attempt: Conversions: `FromLossy` and `TryFromLossy` traits by dhardy ยท Pull Request #2484 ยท rust-lang/rfcs ยท GitHub

Like I proposed WrappingFrom in `num::WrappingFrom` trait for conversions between integers by scottmcm ยท Pull Request #3703 ยท rust-lang/rfcs ยท GitHub, I often wonder if a bunch of this is better handled with numeric-specific traits in core::num rather than just TryFrom.

Traits for lossy, wrapping, saturating and/or rounding conversions between numerics seems to me like they'd be a good fit for putting in core::num, but imo this is the natural meaning for TryFrom between an int and a float and implementing this would be much easier than designing a new suite of traits, so I'd like if this change could be kept small and those be done in some other work effort.

1 Like

Previous attempts and discussion, note that this was before casting floats to ints was made sound.

https://internals.rust-lang.org/t/how-should-we-provide-fallible-float-to-int-conversions/6708


Since there isn't one obvious implementation of TryFrom, that seems too contentious to pursue.

Taking recent features like exact_div and exact shifts as naming inspiration, this might be better proposed as something like (checked_)to_float_exact and (checked_)to_int_exact for integers and floats of the same bit size?
The naming would better indicate that no rounding is taking place.

I've wanted this behavior in the past but never got around to writing an ACP until now. It relates to this topic by adding associated constants to floats for the max integers that they can represent. Those constants can be used as building blocks for handling lossless conversion.
https://github.com/rust-lang/libs-team/issues/713

For float-int conversions, a decision would need to be made whether exact conversion for values larger than ยฑ2.pow(<$float>::MANTISSA_DIGITS) are valid. I would lean towards those returning nothing or an error.

1 Like