-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Implement TryFrom for float to integer types #47857
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @BurntSushi (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
IIUC this would mean all rounding would result in an error? e.g., |
Also for the record the current implementation indeed isn't sound bc of #10184 but let's worry about the implementation once we agree on what should even be implemented. |
Rounding and other destructive operations seems like something you could comfortably do outside of conversion (e.g. For the kind of use cases I have in mind, consider this GitHub search (round then cast): At the same time I agree that the exact meaning of "conversion" isn't strongly defined. So I know at least I would still have a need for these kinds of conversion methods, even if they take some other form. |
I didn't understand why lossless |
@udoprog Note that float->int conversions don't round in the same sense as Regardless, I still don't see the use case for non-rounding ("non-destructive") casts. Your link shows use cases for rounding casts, though rounding differently than the default cast. It's true that the floats that get casted there should be integers already and thus those snippets could use the proposed TryFrom impl. However, one usually uses checked conversions when there's a realistic possibility of information loss -- in these cases, it's trivial to see that the casted value has no fractional part by just looking at the code, so the sole motivation for the checked cast would be erroring on infinities and NaN, which would also be served by a checked cast that, well, checks just for that and permits rounding. But @petrochenkov has a good point. Do we really want, for example, a function that takes a |
@rkruppe Yeah. I was using your example. But as you noted, it works the same regardless of operation (round, floor, ceil, ...). This is also the reasoning I use to not include some version of it in the conversion. In the proposed change any of them can be used to perform the round-trip check.
I noticed a Jula Lang contributor commented on one of the bug reports I was digging through, and they appear to support this kind of conversion the way it is proposed here through @petrochenkov If you are referring to
Silently, as in the compiler not complaining? I must admit this is the argument that made me doubt the validity of this PR the most. But ultimately EDIT: A similar issue was discussed for
Would a similar reasoning apply to this PR today? |
I'm interested in staying in the loop on this discussion -- I've been working on a similar change for the |
The question is whether there's any legitimate use case for doing this conversion without first rounding to a nearby integer. If there isn't (and I believe there isn't), it might be an attractive nuisance to have this half-working method -- people might use it on a toy example and not realize they generally need to add a rounding step manually to get
No debate about Infinities, NaN and out of bounds values. I'm not proposing
Interesting. I looked through a couple pages of github code search, my query wasn't very good so most results were unrelated but I did see some uses of this functionality. 🤔 Some were difficult to make sense of without reading all the surrounding code. I did see
Interesting argument, but I'm not quite convinced. If Basically, this sort of conversion seems more appropriate for monomorphic code rather than for generic APIs. |
To try to keep things in check, there were two things being discussed:
To try to summarize the first point, these are the questions I'm currently considering. It might be useful as a focal point:
It seems you perceived I was using at as argument against rounding. It was actually a response to this:
So, are we OK with generic functions using TryFrom to accept both integers and floats? |
Because there are multiple possible ways to have fallible float -> int conversions, whereas there's only one obvious infallible int -> float conversion, I don't believe these two questions can be decoupled entirely. In particular, whether "it's OK for a generic method accepting TryFrom to take both integers and floats" depends on what it means for a function to accept a float and convert it to an integer with
Edit: Not to say the answer to the second question is necessarily different depending on whether TryFrom rounds (in fact I previously argued it isn't), but rounding influences why one might say yes or no to the second question. |
Ok! I agree. But that does seem to indicate that we should decouple them, since your stance on the second question ( |
There's a dependency but that doesn't necessarily mean it's best to settle the first question entirely before we turn to the second question, since if the answer to the second question is "no" and we don't implement TryFrom at all, the previous discussion was for naught. Well, it could inform the design of infallible conversions that don't use |
So this is me trying to wrap my head around the problem. Instead of trying to justify that there are float-based operations that would fall outside of exact conversion, I'll try to discuss whether it's sensible to pick a default policy to begin with, and what Which rounding policy should be used?Some GitHub searching results in:
Note that since casting floats in Rust is unsound (#10184), the cases which cast (and trunc) might not constitute an active choice. More “do something which makes this an integer”. This limited dataset leads me to believe that picking one policy runs the risk of making a large fraction of the community unhappy about the choice. Making the conversion fail on inexact values can be a hint to the user that they should pick. This would be unfortunate to do at runtime, which would speak for instead having specialized conversion methods for floats ( Outside of casting (which generally truncs), other strongly typed languages seem to force you to actively pick your policy as well. From/TryFrom has been lossless so farMany (all?) of the std trait implementations are lossless. Rounding a float is primarily a way to make members of one type fit into another. A similar but more extreme example of this would be to clampen or project an integer which is to wide to fit into a narrower sibling. The existing conversion methods do not attempt to “coerce” types in this manner. It’s unfortunate that this is not stated clearly, since we are now having to deal with what users would expect out of the conversion traits, which is hard to quantify. But float conversions might also be so special that “lossless conversion” isn’t meaningful enough to warrant inclusion through My personal observation is that the existing fallible conversion methods fail when information would otherwise be lost. Forward?If this change was just the introduction of a couple more |
Yeah I think this summarizes a large part of my position towards this. You give good reasons that the TryFrom impl should not round, and if it did round it would not be obvious which choice would be the most generally useful, so I'm strongly leaning towards not providing these impls at all. Maybe one step before a full RFC would be soliciting input from more people, on internals.rust-lang.org or in an issue? |
A tricky case to consider here is |
Per discussion in IRC and advice from @Mark-Simulacrum, I am tagging this as @udoprog Please keep your eye on that rust-lang internals thread; if and when a consensus is reached and you've implemented the desired functionality, ask for a re-review. |
@udoprog ping from the release team :) It seems that discussion on rust-lang internals stopped a while ago, any plans regarding this PR? |
@arazabishov I'd propose closing this. I might attempt to be resurrect this as a number of specialized methods for |
@udoprog closing this PR for now then. |
I can certainly see a use case for this. I for one, spurred on by clippy, already use either Casting between floats and integers is the last bastion of
My personal design preferences:
It should be implemented between |
I'm flabbergasted that this was closed. It seems obvious to me that I work with numerics, and there are absolutely many use cases for fallible exact conversion from integer to float or vice versa. Let me give my most recent (real-world) motivating example: We're parsing a matrix stored in some format. We expect the matrix to be an integer matrix, but the format doesn't clearly distinguish between integers and floating-point values (perhaps even stored as I urge you to reconsider this issue: in my mind the right thing to do here truly is unambiguous: if an integer can be exactly represented as a floating-point value, then convert, otherwise fail. And vice versa for float -> int. |
Any progress on this ? Any new issues to continue the discussion on ? I also have a use-case for exact matching floats to integers in my project. I had to define a whole new Trait for this to work in my crate, and implement it for floats and numbers as below in a macro : impl TryConvert<TFlt> for TNum {
fn try_convert(self) -> Result<TFlt, ConvertionError> {
let ret = self as TFlt;
if self == (ret as TNum) {
Ok(ret)
} else {
Err(ConvertionError::out_of_precision::<TNum, TFlt>(self, ret))
}
}
} The documentation for
We can argue that "The range of the target type" for an integer is an exact integer, which means the target should not accept non-integers values even if they reside within the MIN..MAX range of the type, which includes something like |
This implements
TryFrom
forf32
, andf64
to all integer types.This is accomplished by casting the floating point number, into the target type and back, and then comparing if the resulting value is identical to the converted value.
I'm not sure this implementation is sound, in particular issues like #10184 seem to indicate that these kinds of casts are not always sound. A review by someone who knows more about floating point numbers, and what kind of guarantees the casts give us would be highly appreciated.
If there is a better way to determine if a given floating point number can be converted into an integer, I'd also love to know.