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

Idea: NonZero-like float wrappers #2458

Closed
clarfonthey opened this issue Jun 3, 2018 · 13 comments
Closed

Idea: NonZero-like float wrappers #2458

clarfonthey opened this issue Jun 3, 2018 · 13 comments
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@clarfonthey
Copy link
Contributor

Basically, the types NonNanF32/64 and FiniteF32/64. The main reasoning of having these in the standard library is to allow niche optimisations.

Additionally, both of these types could implement Ord as NaN wouldn't be valid for either.

@glaebhoerl
Copy link
Contributor

I definitely think this would be a good idea!

See also:

#1249

https://internals.rust-lang.org/t/pre-pre-rfc-range-restricting-wrappers-for-floating-point-types/6701

@eddyb
Copy link
Member

eddyb commented Jun 4, 2018

Could we wait for a more general system based on const generics?

@glaebhoerl
Copy link
Contributor

Isn't that kind of an Ouroboros - we want to use const generics to exclude NaN from floats but we can't use floats in const generics because of NaN?

@eddyb
Copy link
Member

eddyb commented Jun 4, 2018

@glaebhoerl Such a system would likely need to use integer ranges of the bitwise representation anyway, to properly express what they need to do.

@scottmcm
Copy link
Member

scottmcm commented Jun 8, 2018

I'd like NonNanF32/64 soon, because they can be Ord, and that could give a nice efficient type-based solution to the "why can't I sort my slice of f32s?" question.

I'm not sure that the Finite ones need the layout optimizations, especially not urgently, since there's so much NaN space compared to the only two infinities, and ±∞ aren't a problem for Ord/Eq. (And, more vagely, I'm not sure what code is totally fine with 1.8e308 but can’t handle +∞.)

@hanna-kruppe
Copy link

My gut feeling is that we shouldn't necessarily mix up these two things:

  1. Newtypes which impose some logical invariant on the wrapped type to rule out logically invalid or dubious states
  2. Restrictions on valid bit patterns to enable layout optimizations

I don't have super great rationale for that at the moment, but I can vaguely gesture at the following observations:

  • Logical newtypes (1) can have logic bugs and those may lead to a buggy overall program depending on the bug and how the newtype is used in the program, but for niches (2) even just producing an invalid bit pattern anywhere is instant UB.
  • At least the examples of (2) that we have today are often used in critical unsafe abstractions (because they allow making key data structures more compact) which are full of unsafe code anyway and don't want to pay for unnecessary safety checks. Thus there's a real need for unsafe constructors like NonNull::new_unchecked there. In contrast, it seems to me that most use cases of (1) – which should be a roughly representative sample of all Rust programs out there – can probably afford the cost of validation and so (like most code) should prefer panics to UB as consequence of programmer errors.
  • Logical new types (1) may want to offer a quite extensive and opinionated API surface mirroring the underlying type's operations. In contrast, the "niche types" (2) we have so far have a very minimal API because they're just for storage. If you want to operate on a NonNull or NonZeroU32, you're supposed to extract the actual *mut T/u32 value, operate on it as usual, and if necessary wrap the result in the niche type again.
    • For floats in particular, one could (for example) imagine a NonNan type which provides the usual arithmetic operations and panics if a Nan is produced (analogous to integer overflow checking). Data structures storing floats that just want to provide niches probably don't want all that baggage.

It seems the underlying theme to what I'm saying is something like: the ideal "this bit pattern can never occur, if it does it's UB" API might look rather different from the ideal "i want to make sure to rule out this invalid value, if i mess up give me a nice error" API.

So I'd recommend that we make progress on the general (const generics, bit pattern range based) niche system @eddyb mentioned for providing layout optimizations. Independently, people can continue1 to experiment with APIs for logically-never-NaN newtypes, and when the aforementioned general niche system stabilizes, some of those library types can choose to use it to provide niches in addition to their logical invariants.

1 There's a lot of work in this area already. A quick search for "ord float" on crates.io gives: ordered-float, float-ord, float, decorum, ord_subset, new-ordered-float, noisy_float, and fin. I've not looked at how all of those work but they all claim to address the problem of sorting a collection of floats in some way.

@Centril Centril added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Oct 14, 2018
@clarfonthey
Copy link
Contributor Author

Going to close this in favour of an ACP: rust-lang/libs-team#238.

@SOF3
Copy link

SOF3 commented Jun 10, 2023

Aren't there a lot of values in the infinite/nan classes that are identical? could those be exploited instead?

@programmerjake
Copy link
Member

Aren't there a lot of values in the infinite/nan classes that are identical? could those be exploited instead?

no, none of the different Infinity/NaN values are identical. code can tell them apart by inspecting their bits. also, things like total_cmp or copy_sign behave differently.

@clarfonthey
Copy link
Contributor Author

(Small correction: there are exactly two infinities with different values because they have different signs, so, those can be told apart. But NaN has an absurd number of values that have totally different bit patterns, and this is a source of a lot of pain for the folks trying to determine float semantics in Rust.)

@programmerjake
Copy link
Member

(Small correction: there are exactly two infinities with different values because they have different signs, so, those can be told apart. But NaN has an absurd number of values that have totally different bit patterns, and this is a source of a lot of pain for the folks trying to determine float semantics in Rust.)

those NaNs can also be distinguished using the methods I mentioned in #2458 (comment). that said, LLVM doesn't guarantee which NaNs it will produce for arithmetic operations and iirc some by-value copies (mostly function returns on x86-32) so may produce any arbitrary NaN.

@CraftSpider
Copy link

Optimization strategies such as NaN-boxing rely on being able to introspect NaN values and treat them differently (though a SingleNaNF64 could be useful by telling the compiler what bit pattern you've chosen as 'canonical' and letting it just niche-optimize things into the NaN space in a completely normal enum, normalizing any other NaN values into that one)

@scottmcm
Copy link
Member

My gut feeling is that we shouldn't necessarily mix up these two things:

1. Newtypes which impose some logical invariant on the wrapped type to rule out logically invalid or dubious states

2. Restrictions on valid bit patterns to enable layout optimizations

Note that restrictions on valid bit patterns would also be needed to be able to use things like the nnan flag in LLVM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

9 participants