-
Notifications
You must be signed in to change notification settings - Fork 19
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
ACP: NonNanFNN
and FiniteFNN
float wrappers
#238
Comments
It might be interesting to explore what using, say, the Unfortunately, neither non-NAN nor Finite are closed for floats, so these types if they had even As such, I suspect that the most useful versions are the |
I had no idea about the That said, while I get that finites aren't closed under most operations (you can easily add/multiply finite numbers and get an infinity), I thought that at least non-NaN operations were? Like, obviously finites aren't closed under operations like |
And |
0/0 is even an operation on non-NaN finites that doesn't close. |
Right, in those cases |
I've wanted something like this quite a few times, but in a way that every single NaN representation will be interpreted as The compiler currently doesn't support that. But the advantage would be that |
How would Ord behave for positive and negative zero? Would those compare equal? If so, f32::total_cmp could still be preferrable when using floats as keys in a BTreeMap. |
Wouldn't the NonNan types actually cause more special casing? If a processor natively supports operations like addition/multiplication/division/etc. that can result in NaN, using NonNan types would mean extra branches to handle those results separately, right? |
That would work for something like enum Thing {
Float(NonNanF64),
False,
True,
FileNotFound,
} but for most such 'nan boxing' optimizations I've seen in the wild, you want to be able to make use of all 52 bits, which won't happen through a simple enum like that, unless we add some magic compiler support and a special 'only NaN' type like:
Also, most cases I've seen still want to support actual NaN, so they still reserve one bit pattern for f64::NAN and using the remaining NaN representations for other values. For those cases, NonNanF64 isn't what they need. So for 'NaN boxing' I'm not convinced |
This is a good point; I was considering them as being treated equal.
So, my thought process is that this only reduces special-casing for inputs, and not for outputs. So, these operations could output NaN floats, but at least any special-casing of what happens for input NaNs wouldn't be necessary. But if you want to make it a closed loop, you're right that it would require extra branches. |
But that just moves the checks elsewhere, right? Is that by itself very useful? Do you have any example use cases where these types would help with performance or correctness while also making good use of the NaN niche optimization*? (* Because that's the reason this currently can't be done outside core/std.) |
So, while it is the one example of something that got tossed out of libstd, I keep thinking back to lerp because it's a good example of something well-behaved that could really benefit from these sorts of optimisations. While the fundamental operations can overflow to infinites or lead to NaNs in some cases, if you're careful about how you deal with them and have bounded inputs, you're effectively guaranteed to end up with bounded outputs. Interpolation in general is probably the most common case here, since it's something that requires having a lot of bounds checks on inputs, but by its nature bounds outputs to inputs, and thus also satisfies the closure property. Effectively, the unboundedness of the outputs of fundamental operations is resolved by additional unsafe assertions, rather than branches. Effectively, you can "get past" the extra branches on the outputs with assertions, but you can't easily do this on inputs without the compiler support. Maybe some sort of finagling with assertions can coerce the existing compiler to optimise properly today, but at least I'm not aware of any. Another potential example on my mind that actually exists in the standard library is |
println!("{}", f64::MAX.is_finite()); // true
println!("{}", f64::hypot(f64::MAX, f64::MAX).is_finite()); // false |
We discussed this proposal in the most recent T-libs-api meeting. We discussed each of the motivations, and came away not convinced that these additions to the standard library would be worthwhile.
While we're not interested in |
Proposal
Problem statement
A lot of the existing code for floating-point values will special-case infinite and NaN values, and it's desirable to be able to specify at the type level that these values cannot occur. Additionally, the ability to remove NaN values from floats would allow the ability to implement
Ord
for these types, enabling their use in many APIs without relying ontotal_cmp
.Motivating examples or use cases
Ord
invariants, such as the keys of aBTreeMap
or the values in aBinaryHeap
.Notes on optimisation as motiviation
One potential benefit of these types is the ability to add additional optimisations to them. For example, operations like
sin
,cos
, andhypot
could all be modified to return values that are explicitly non-NaN to avoid additional checks for NaN in code.However, note that these cases can be relatively niche and it's not clear how many of these would be actually useful. Additionally, operations that are more complicated like linear interpolation would have to be included in the standard library, and we've already mostly decided that these operations are not a good fit for the standard library due to their complexity.
Additionally, many operations would only be easily optimised if the floats were further constrained to be explicitly positive, non-zero, non-one, or bounded in some other arbitrary way. This definitely reduces the usefulness of this avenue of optimisation, and it feels best to only analyse these types as being useful specifically for their invariants, rather than for their ability to optimise various mathematical operations, at least within the standard library. Obviously, downstream crates can leverage these types for their own optimisations, assuming that they're okay with using unsafe code.
Solution sketch
Four types would be added:
NonNanF32
andNonNanF64
forf32
andf64
types which exclude NaN values, andFiniteF32
andFiniteF64
for types which exclude both NaN values and the two infinities.The minimum API for these types should mimic that of the
NonZero*
types, namely (usingNonNanF32
as an example):Additionally, these would implement all of the common traits from their underlying floating-point types (
PartialEq
,PartialOrd
,Copy
,Debug
, etc.) but also implementEq
,Ord
, andHash
due to the lack of NaN values. Unlike theNonZero*
types,Default
would be fine, since zero would still be included.These types would additionally include niche areas for the forbidden values, which is possible due to the fact that non-finite values are represented by two continuous ranges (one for each sign) when treated as integer bits. The infinities also exist at the edges of these ranges adjacent to the finite values and can easily be represented.
Alternatives
The primary alternative would be to simply avoid adding these types, as there are many crates that already exist to provide these types. The largest benefits of a standard library implementation would be to standardise these types, provide niche optimisations (at least until a stable avenue exists), and to potentially optimise various mathematical operations on these types with compiler help, although that last bit is disputed in the motivation section.
Links and related work
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution:
The text was updated successfully, but these errors were encountered: