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

Tracking Issue for float_next_up_down #91399

Open
1 of 4 tasks
yaahc opened this issue Nov 30, 2021 · 19 comments
Open
1 of 4 tasks

Tracking Issue for float_next_up_down #91399

yaahc opened this issue Nov 30, 2021 · 19 comments
Labels
A-floating-point Area: Floating point numbers and arithmetic B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC I-libs-api-nominated Nominated for discussion during a libs-api team meeting. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@yaahc
Copy link
Member

yaahc commented Nov 30, 2021

Feature gate: #![feature(float_next_up_down)]

This is a tracking issue for two argumentless methods to f32/f64, next_up and next_down. These functions are specified in the IEEE 754 standard, and provide the capability to enumerate floating point values in order.

Public API

impl f32 {
    pub const fn next_up(self) -> Self;
    pub const fn next_down(self) -> Self;
}

impl f64 {
    pub const fn next_up(self) -> Self;
    pub const fn next_down(self) -> Self;
}

// Type is unstable
impl f16 {
    pub const fn next_up(self) -> Self;
    pub const fn next_down(self) -> Self;
}

// Type is unstable
impl f128 {
    pub const fn next_up(self) -> Self;
    pub const fn next_down(self) -> Self;
}

Steps / History

Unresolved Questions

@yaahc yaahc added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. labels Nov 30, 2021
@orlp
Copy link
Contributor

orlp commented Feb 19, 2022

During merging of my implementation a new unresolved question popped up due to failed tests:

What should we do when the platform flushes denormals to zero and/or does not preserve NaN payloads?

I thought that my implementation would be immune to these issues since I call to_bits() at the start, from_bits() at the end and perform all logic with integers. Apparently not so.

For example, it appears that Rust on some x87 platforms flushes denormals and destroys NaN payloads on function boundaries. x.to_bits() == x.to_bits() can succeed, but identity(x).to_bits() == x.to_bits() can fail. Since next_up is a function boundary itself, it can not work as advertised for some inputs, even if the user does not perform any floating point operations with the resulting value. This also can form infinite loops when repeatedly calling next_up until an upper bound is reached, if the range crosses the denormals.

I don't know what the right way to handle this is. Maybe a careful look needs to be taken at when Rust stores data in floating point vs integer registers when to_bits and from_bits is involved on platforms where this has implications for the data. Or maybe the interface for next_up and next_down needs to be changed so that x.next_up() becomes f32::next_up(x.to_bits()).from_bits(), and loops can store their state in a u32 rather than f32.

Alternatively we add a disclaimer to the documentation for next_up/next_down that it may fail to uphold its contract on platforms that flush denormals and/or do not respect NaN payloads, or disable the function entirely. Neither of these makes me particularly happy.

@thomcc
Copy link
Member

thomcc commented Feb 19, 2022

CC @workingjubilee who has recently been thinking some about platforms like x87 and such.

@cmpute
Copy link

cmpute commented Nov 6, 2022

What are the common usages for these two functions? I met a use case, that is to parse a float into a rational number with simpler form, which requires me to get the rounding interval. This can be done using these two functions.

This also can form infinite loops when repeatedly calling next_up until an upper bound is reached, if the range crosses the denormals

Is there anybody actually enumerating floats like this? In my case it's okay to document the behavior that the function will skip subnormals in certain platforms.

(Note: IEEE 754 doesn't have any explicit rule about executing nextUp and nextDown on subnormals)

@sharifhsn
Copy link

I can comment on my use case for this function. I'm doing research on high-performance floating-point operations that requires me to iterate through floating-point numbers in this way to determine intervals for an input. My current workaround is manually converting using to_bits and from_bits, but these have the disadvantage of being awkward and not necessarily complying with IEEE-754 recommendations.

In my work we already treat denormals as a special case, so it's fine if next_up and next_down cause unexpected behavior when crossing the denormal boundary; I avoid doing that already because it's so error-prone.

Thanks for the work on these new functions, they are a welcome ergonomic improvement for me! I hope that they are stabilized soon.

@workingjubilee
Copy link
Member

(Note: IEEE 754 doesn't have any explicit rule about executing nextUp and nextDown on subnormals)

I am not sure what you mean by this, as implementations that do not implement subnormal numbers are nonconformant with IEEE754. I intend to address these concerns and more in a future project soon.

@cmpute
Copy link

cmpute commented Nov 22, 2022

I mean AFAIK IEEE 754 doesn't specify how subnormals are handled with nextUp and nextDown.

@peckpeck
Copy link

I would expect
assert!(x.next_up() > x);
ie
assert!(-1_f64.next_up() > -1_f64);

But this is not the case, either the documentation is wrong or the implementation.

Either way, shouldn't there be a test?

About usage, this can be used to estimate rounding on operations.

@orlp
Copy link
Contributor

orlp commented Nov 27, 2022

@peckpeck Which platform are you testing on? I can not reproduce your problem.

@sharifhsn
Copy link

sharifhsn commented Nov 28, 2022

@peckpeck I believe you're running into the ambiguity described here. Here is a playground demonstration. The implementation is correct.

@orlp
Copy link
Contributor

orlp commented Nov 28, 2022

@peckpeck Ah, yes, -1_f64.next_up(), sadly, is parsed by Rust as -(1_f64.next_up()). This isn't anything related to next_up however, for example -1_f64.abs() also results in -1. You can avoid this pitfall by writing f64::next_up(-1.0).

@peckpeck
Copy link

Ah indeed, by bad. Sorry for the noise.

@schuelermine
Copy link
Contributor

Why return NAN from NAN? Why not panic?

@sharifhsn
Copy link

sharifhsn commented Jan 25, 2023

Propagating NaN is a basic requirement of IEEE-754 floating point, and you'll find the same feature in basically every floating point library or function that exists. It is the caller's responsibility to handle a NaN result, and they can panic if they wish:

let x = 1f64;
let y = x.next_up();
if y.is_nan() {
     panic!("not a number");
}

or perhaps in a more Rust way,

let x = 1f64;
match x.next_up() {
    y if y.is_nan() => panic!("not a number"),
    y => {
        // do something with y
    }
}

@schuelermine
Copy link
Contributor

OK

@workingjubilee workingjubilee added the A-floating-point Area: Floating point numbers and arithmetic label Jan 27, 2023
@fee1-dead fee1-dead added B-unstable Blocker: Implemented in the nightly compiler and unstable. B-RFC-implemented Blocker: Approved by a merged RFC and implemented. and removed B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. labels Mar 14, 2023
@zakarumych
Copy link
Contributor

One possible use-case is to create smallest non-empty range if given range is x..=x.
By replacing it with x.next_down()..=x.next_up()

@Ralith
Copy link
Contributor

Ralith commented Dec 15, 2024

I wanted this today to take a point in space which lies exactly on an axis-aligned plane, and make the minimal adjustment necessary to move it to one side of that plane.

@tgross35
Copy link
Contributor

tgross35 commented Dec 20, 2024

The discussion at #129835 seems to indicate that we don't know of any platforms for which these functions continue to misbehave, possibly fixed by #123351. I think this means we should be unblocked for stabilization - does this sound accurate? @RalfJung @beetrees

@RalfJung
Copy link
Member

RalfJung commented Dec 20, 2024 via email

@tgross35
Copy link
Contributor

In that case, libs-api could you consider FCP for stabilization?

@rustbot label +I-libs-api-nominated

@rustbot rustbot added the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-floating-point Area: Floating point numbers and arithmetic B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC I-libs-api-nominated Nominated for discussion during a libs-api team meeting. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests