Skip to content

ACP: NonNanFNN and FiniteFNN float wrappers #238

Closed as not planned
Closed as not planned
@clarfonthey

Description

@clarfonthey

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 on total_cmp.

Motivating examples or use cases

  • Inclusion in types which require Ord invariants, such as the keys of a BTreeMap or the values in a BinaryHeap.
  • Storing values which are interpolated in various contexts, such as layout engines or stored statistics, since interpolating these values can cause undesired behaviour.
  • Floating-point values stored in enums which leverage niche optimisations, reducing memory usage by storing sentinel NaN values.
  • High-performance floating-point code which wishes to avoid special-casing NaN and infinite values.

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, and hypot 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 and NonNanF64 for f32 and f64 types which exclude NaN values, and FiniteF32 and FiniteF64 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 (using NonNanF32 as an example):

pub const unsafe fn new_unchecked(x: f32) -> NonNanF32;
pub const fn new(x: f32) -> Option<NonNanF32>;
pub const fn get(self) -> f32;

Additionally, these would implement all of the common traits from their underlying floating-point types (PartialEq, PartialOrd, Copy, Debug, etc.) but also implement Eq, Ord, and Hash due to the lack of NaN values. Unlike the NonZero* 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):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions