-
Notifications
You must be signed in to change notification settings - Fork 20
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
float::not_nan #507
Comments
On a related note: We already have the |
https://internals.rust-lang.org/t/pre-rfc-nonnan-type/8418/25 |
I like So perhaps this should be an IsNan trait that requires an |
Since the linked discussion, impl NonNan<f32> {
pub fn checked_sqrt(self) -> Option<Self>;
}
On the other hand, most floating-point operations do not yield NaN as long as all of the operands are numerical. In these cases we can simply define them like we already do with the primitive floats. |
I think we need a stronger motivating example. In most cases, checking for NaN isn't necessary because most float operations will happily take a NaN and just output a NaN in response. In some cases, checking for NaN will even prevent optimizations such as vectorization. |
I think the same can somewhat be said for |
No, currently the compiler will pick exactly one niche value for So, there's no "free" conversion for |
Well, Rust assumes that all FP exceptions are masked and the status bits are ignored, so signalling NaNs are treated as any other NaN and basically all hardware will not cause any traps or go any slower because the NaN happens to be signalling. This is different than division by zero, where x86's division instructions will cause interrupts, and presumably because of that it is UB in LLVM, so checking for division by zero is required. |
Another 2 benefits I mentioned in the other thread are that firstly all NonNan will implement Ord and Eq. Allowing them to be used as keys. Secondly a NaN type lets you create NaNs out of payloads and to get the payload from the NaN allowing for NaN boxing. |
|
It is quite easy to yield infinities from finite numbers (e.g. MAX + MAX = INFINITY) and then yield NaNs from infinities (e.g. INFINITY - INFINITY = NAN). Similar to
Footnotes
|
I was not aware of this. Thank you. This dismisses my point.
Could it be worthwhile creating an exception in this case? Even if this costs us the optimisation at higher, nested levels... But the memory concerns as pointed out by @oshaboy would at least be mitigated with
Wouldn't |
Yes TBH I find it strange that the original request only rejected NaN. For instance, if you are interfacing with standard SQL databases you will need to reject both NaN and Infinity. |
I personally would be for a |
The problem with that will be that you'll have to unwrap it before running any operations on the NonNan. Which like... Why? The hardware already pretty much deals with it as every operation on a NaN results in NaN (besides max for some reason, they fixed it in the 2019 revision though). It already pretty much acts like I think it would be cleaner to just have f32 implement Try as opposed to having a special case in the compiler just for floats. IEEE-754 floats are already option types in all but name. |
You will not need to unwrap it if the compiler can simply interpret it and handle it like an ordinary float (provided the same representation is used). You will need it for getting the result of checking methods (if you're not just using Implementing |
How?
|
This was discussed during today's libs-api meeting and we're going to reject this and suggest to implement this in a crate instead. As the discussion above suggests this would work better with a NonNan type which we have previously rejected too (#238). Perhaps this could even live as a conversion helper in a crate providing NonNan and similar types. Additionally we didn't find the examples particularly convincing. It would make combining several arithmetic operations very verbose and in many cases it'd likely be sufficient to check for NaN at the end rather than having to We also discussed that it would be more palatable if we had pattern types but even those would have difficulty representing |
Yes, I agree that I wasn't very clear on what I was saying. What I think I was trying to say was that
Okay, but it seems I misunderstood your point then. I thought you wanted to completely avoid adding |
The details should probably be left to someone who is smarter and better at Rust than me. I cobbled together a proof of concept library and what I came up with was to split the floats into "NaNable" "NonNan" and "Nan". I can't implement Try directly on the primitives because Rust doesn't let you implement traits unless either the trait or the type is defined in the current crate. |
The problem is crates can't implement Try on floats because neither are local. I tried using a wrapper type but that caused a big nest of |
Would The example in the top post isn't concrete enough to be convincing, I think we would understand the goals a bit better if somebody could point to some examples from the real world. |
Implementing |
Well there's this time a NaN caused a self driving car to crash into a wall If the "adjust steer" function was marked |
Key word "Most". You don't know whether the dev of the library heard of NaNs and also had enough coffee to remember that NaNs need to be checked. Postel's Law kinda requires you to perform two NaN checks, one by the caller and one by the callee. The compiler will optimize it anyway. Also from my testing this can't just be made into a crate. You either have to wrap the float types in a "Nanable" type or just give up on Try which would require non zero-cost conversions to and from Option. Though it could be a case of "I suck at Rust" |
The two aspects here are optimization (by short circuiting) and NaN-safety. My comment was mostly addressing the optimization case, Regarding It is unfortunately that the (I wish there was a better name like |
Yes that's what I am trying to do. You have
The problem with making it a crate is that I think William Kahan never intended high level programming languages to just send around NaNs between functions. It's an assembly level construct for an assembly level language. Sadly it got shoved up the abstraction layers just like machine integers (which are another can of worms) |
Right. But as I mentioned above, this would not be very useful because it provides no additional type safety, meaning the only thing it adds is a way to short circuit.
That mess is exactly what you want if you are trying to enforce NaN safety because it indicates places where use ordered_float::{FloatIsNan, NotNan};
// Demonstrate converting once after operations. This is probably what you want.
pub fn foo(input: f64) -> Result<NotNan<f64>, FloatIsNan> {
let mut x = input.sin();
x = x.sqrt();
x *= 1234.56;
NotNan::new(x)
}
// Check for NaN at multiple points, bail early if found. Not much reason to do this.
pub fn bar(input: f64) -> Result<NotNan<f64>, FloatIsNan> {
let mut x = NotNan::new(input.sin())?;
x = NotNan::new(x.sqrt())?;
x *= NotNan::new(1234.56).expect("1234.56 is not a NaN");
Ok(x)
}
pub fn must_not_be_nan_or_bad_things_happen(x: NotNan<f64>) {
if x.into_inner().is_nan() {
// guaranteed to never happen because of the type invariant
drive_the_car_into_the_wall();
}
} |
No the mess mostly came from conversion between the Nanable wrapper type and the primitive type and back again. That conversion is always safe and zero cost but I can't figure out how to make it implicit. |
It also adds a way for it to interface with Try templates. |
Proposal
Problem statement
We could make working with floats a bit nicer by leveraging
?
Motivating examples or use cases
rust-lang/rust#84277 (comment)
Solution sketch
The example would look like the following:
Alternatives
This could also be an extension trait implemented in a third party crate.
The text was updated successfully, but these errors were encountered: