Repository navigation
Tracking Issue for float_minimum_maximum #91079
Copy link
Copy link
Open
1 / 11 of 1 issue completedLabels
A-floating-pointArea: Floating point numbers and arithmeticArea: Floating point numbers and arithmeticC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.
Description
Feature gate: #![feature(float_minimum_maximum)]
This is the tracking issue for the f{32,64}::{min,max}imum functions.
It provide IEEE 754-2019 minimum and maximum functions to f32/f64.
Public API
impl f32 {
pub const fn minimum(self, other: f32) -> f32;
pub const fn maximum(self, other: f32) -> f32;
}
impl f64 {
pub const fn minimum(self, other: f64) -> f64;
pub const fn maximum(self, other: f64) -> f64;
}Steps / History
- Implementation: Adds IEEE 754-2019 minimun and maximum functions for f32/f64 #91008
- Constified in Make some float methods unstable
const fn#130568 - Final comment period (FCP)
- Stabilization PR
Unresolved Questions
- Should the implementation be stabilized in it's current state (ie. without intrinsic: explanation and llvm issue) ?
- Use intrinsics for
{f16,f32,f64,f128}::{minimum,maximum}operations #140792 - [aarch64]
Cannot select: constant:i128<0>forllvm.maximum.f128llvm/llvm-project#139380 - [x86]: Cannot select:
constant:i128<0>forllvm.maximum.f128llvm/llvm-project#139381 - [arm] Cannot select
i32 = setcc 0x00000000, Constant:i64llvm/llvm-project#140445 - as of 2026-06-27, we are using the intrinsic for f16 and f32 but not for f64 and f128
- Use intrinsics for
- Should it be called like IEEE 754-2019 (
minimumandmaximum) or something else ? Tracking Issue for float_minimum_maximum #91079 (comment) - Should the standard library have this function at all (we already have
min/max) ?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
A-floating-pointArea: Floating point numbers and arithmeticArea: Floating point numbers and arithmeticC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.
Activity
CryZe commentedon Nov 21, 2021
Just so it's tracked here: This currently does not compile to the corresponding instruction on WASM (fmin / fmax) and I believe Aarch64 has an instruction for this too (FMIN / FMAX), which it also doesn't compile to.
Godbolt
workingjubilee commentedon Nov 21, 2021
I believe there is an LLVM instruction for this. We can teach Rust to emit the appropriate LLVMIR directly, at the cost of adding the weight of an intrinsic. As we have discussed doing so for the portable SIMD API anyways, it might be profitable to do this in general.
scottmcm commentedon Nov 22, 2021
LLVM does have an intrinsic for this, but as the PR that added these showed (https://godbolt.org/z/czz71M9KT), it doesn't actually work yet, which is why rustc isn't using it to implement these.
workingjubilee commentedon Nov 26, 2021
Ah, Damn.
rben01 commentedon May 15, 2022
Everything that follows will be stated in terms of the
minandminimumfunctions but applies equally tomaxandmaximum.Does the function have to be called
minimum? I know that's the IEEE name but I think I'd find having bothminandminimumconfusing, as their names don't really say which is which. "The function that requires both inputs beNaNfor its output to beNaNis the one with the shorter name" is not a natural rule to remember IMO. I think I'd prefer havingminimumbe called (say)partial_minto suggest that it follow the same rules asPartialEqandPartialOrd, which is that a singleNaNin the input is sufficient to produce a missing output.m-ou-se commentedon Aug 16, 2022
Nominated at the request of @Urgau.
m-ou-se commentedon Aug 23, 2022
The confusion between
maximumvsmaxis definitely a problem, so we'll need to do something about that before we can stabilize that. Themaxandminfunctions are already stable, so we have limited flexibility.We'll need some more input: How do other languages and libraries handle this? How are these named in the standard? What are the use cases for
maxvsmaximum?alistaircarscadden commentedon Aug 25, 2022
I agree that having pairs of functions such that
min != minimumandmax != maximumis an issue. I had to check the documentation for why there were two functions to do the same thing to realize that they were functionally different. I think whatever the most explicit possible name is should be chosen. I don't thinkminimum / maximumis correct.Here are some suggestions to add to the brainstorm. Any of these can be
minorminimumand apply to bothminandmax.min_nan,min_propigate_nansmin_any_nanmin_ieee_754_2019min_nans_winI think that
minwith some suffix is preferable tominimumbecause it shows it is an alteration of the existingminfunction, not a competing function. I don't know the whole history of relevance of IEEE 754 2019 but I think choosingminimumbecause that's how it is named in that standard is not clear to the average Rust programmer.rben01 commentedon Aug 25, 2022
To throw some other ideas in the mix:
By analogy with the distinction between quiet NaN and signaling NaN, maybe the new minimum (maximum) function should be called
signaling_min(signaling_max).I also like
lesser_of(greater_of), as it kind of implies that<(>) will be used, which obviously return NaN if at least one argument is NaN. You could imagine the following conversation: "Which is the lesser of 1 and NaN?" "That makes no sense, they can't be compared." "Exactly."IMO
min_all/min_both(max_all/max_both) also get the idea across -- all/both of the arguments will affect the output. In particular, even if one of the args is -inf (+inf) you still have to look at the other.CryZe commentedon Aug 25, 2022
This might also influence the naming situation: There's more than just these two algorithms.
Rust's max is IEEE Std 754-2008 maxNum
This new one is IEEE Std 754-2019 maximum
And there's also IEEE Std 754-2019 maximumNumber
And I feel like WebAssembly's pmax / C++'s std::max is also worth considering having (ignores NaN for a fast impl)
scottmcm commentedon Aug 25, 2022
The new one is arguably the "best" one, as it's the one that behaves most similarly to everything else with respect to NANs -- the other binary functions like
addandatan2do the same "NAN if either input is NAN", same as whatmaximumdoes.So one option might be to deprecate
maxunder that name and offer it asmax_ignoring_nansor something, letting the new one get the namemaximum.(Thought that does have the obvious downside of being different from the
maxmethod on everything that'sOrd.)Urgau commentedon Aug 25, 2022
C/C++/Swift doesn't (yet ?). C#/Python have the IEEE 754-2019
minimumandmaximumsemantics as theirmin/maxfunction from the start. BUT there doesn't seems to have any language that have both the 2008 and 2019 semantics.They are named
minimumandmaximumin IEEE 754-2019.Note that the previous functions
minNumandmaxNumfrom IEEE 754-2008 (and below) are deprecated in IEEE 754-2019 in favor of the new one.(I don't really understand the question) but I would say none. Only one should exist, in IEEE 754-2019 they deprecated
minNumandmaxNumin favor ofminimumand `maximum, maybe we should do the same.+1
Let me know if you need more input. Happy to help.
30 remaining items
valadaptive commentedon Nov 30, 2025
Yes. I don't think the nondeterminism here is a big deal, since you can just add 0.0 to the result to convert negative zero to positive zero.
valadaptive commentedon Dec 1, 2025
The aarch64 lowerings of
minimumNumberuse a routine in isolation, but that's just to handle the possibility that an input is a signaling NaN. That's because ARM implements the IEEE754-2008 version, which returns a qNaN if either input is a sNaN. LLVM is "canonicalizing" the input operands, ensuring that any sNaNs become qNaNs. If the operands are the result of basically any other floating-point math operation, LLVM should already know that they cannot be sNaN, and won't have to emit the extra operations.The x86 lowerings are quite bad for the 2019 operations right now (tons of branches), but I have an LLVM PR that should improve the situation to "routine (short)". They still won't be as short as the
minNumlowering, but they at least won't require branches.The lowerings can also be improved quite a bit if one operand is a constant, since you know in that case it's not NaN, and can reorder things to get proper signed-zero behavior.
I think the answer here might depend on how people use these operations in practice. I'd like to survey some Rust code and see how people use
minandmax--do they typically call them with constant operands? If so, the performance hit from emulating IEEE754-2019 behavior might be acceptable, since it'll be required less often. How do people typically conduct these types of code surveys?RalfJung commentedon Dec 1, 2025
@tgross35 does that table take into account the behavior for SNaN? Apparently, on aarch64, the minNum instruction returns a qNaN for
minNum(sNaN, 0.0), which would not be what we currently document for ourminto do.EDIT: Ah, I think that is what @valadaptive also referred to, except for the context of
minimumNumber.[X86] Lower `minimum`/`maximum`/`minimumnum`/`maximumnum` using bitwi…
Automerge: [X86] Lower `minimum`/`maximum`/`minimumnum`/`maximumnum` …
ickk commentedon Feb 1, 2026
On the contrary I would really like to have some way to get the IEEE754-2019
minimumbehaviour (propagate NaNs).Putting aside (sometimes questionable) choices that any particular ISA makes, it seems to me that propagating NaNs is more often the strictly correct thing to do from the perspective of numerical procedures; If mathematically the operation is not defined, for correctness I need to be able to observe that somehow; currently I have to manually insert these checks everywhere a min/max appears. Most other operations already do propagate NaNs, which allows deferring the handling of these edge-cases until the end of the procedure. In my opinion this usually makes for simpler and more maintainable code.
I really don't see the semantic utility in Rust having both
min(2008) andminimumNumber(2019) where they would differ only in the treatment of signed zeros. If your code is so sensitive to the sign of the zero, it almost certainly also cares about the presence of a NaN. Are you able to describe situations where you would need the semantics of bothmin(2008) andminimumNumber(2019)?If your desire instead comes from just wanting to have one cheap option available to most platforms, and you specifically don't care about the signed zero edge case, then I'd rather propose something like the following:
strict-minimum: NaN propagating. Conforming to IEEE754-2019 minimum, andrelaxed-minimum-number: NaN absorbing. The sign of a resulting zero explicitly not defined.I think the latter would more accurately capture this intent, since the compiler would have the freedom to automatically make the choice for the faster variant based on the target-platform - resulting in code that is more portable. Otherwise libraries are still going to have to
cfgto choose the fastest variant, which isn't any better than using instrinsics. I would also support adding platform specific variants of this kind of thing tostd::archone way or another, rather than having Rust choose a favourite arch at the expense of useful semantics.Apersoma commentedon Mar 17, 2026
this should probably change the deprecation message of
abs_subto usemaximuminstead ofmaxbecause it correctly propagates nan.[X86] Lower `minimum`/`maximum`/`minimumnum`/`maximumnum` using bitwi…
nickspiker commentedon Jun 23, 2026
The missing intrinsic (#140792) isn't just an ergonomics issue, it gates codegen quality which is a concrete reason to prioritize it and of course the ordering problem where x.min(a).max(b) is not equivalent to x.max(b).min(a) but only when NaN is involved.
I went down this from a random image pipeline and disassembled the results as it is common to see naieve implementations like the clamp then cast (nightly
1.97.0,aarch64-unknown-linux-gnu,-O). The underlying gap is that no mainstream ISA has the IEEE 754-2019 propagating min/max in hardware — they ship the deprecated 754-2008 NaN-suppressing op (fminnmon aarch64). So:f32::min(suppressing) lowers to one instruction class:f32::minimum(propagating — the correct op) has no single instruction, so it open-codes to a branch tree (~17 insns:fcmp/b.mi/fmov/cmn/ccmp/fcsel/fadd):The downstream cost: that open-coded blob also defeats the
fptoui.sat-of-clamp peephole.x.maximum(0.0).minimum(255.0) as u8is provably identical tox as u8for every input (NaN→0 both ways via the saturating cast; >255→255; <0→0), yet LLVM emits ~17 branchy instructions for the clamped form vs 4 for the bare cast. It can't see thru the branch tree to fold the now-dead clamp. Withllvm.minimum/llvm.maximumintrinsics, both the direct op and this fold should collapse to the single-instruction path.So #140792 looks like the real lever: wiring the intrinsics unblocks correctness-availability and codegen together. Given the fallback impl is already correct, would it be reasonable to stabilize on the fallback now (correctness win, available on stable) and treat the intrinsic as the follow-up that recovers codegen? Today, reaching for the non-suppressing op requires nightly and is slow even there — a self-reinforcing reason people keep defaulting to the suppressing
min/max.RalfJung commentedon Jun 23, 2026
You seem to have replied to an outdated part of the tracking issue. I have updated the "unresolved questions". I don't think anyone is concerned about the intrinsic for stabilization.
EDIT: Actually, the intrinsic isn't used as it is buggy. But I agree with you that this should not block stabilization, and I don't think the team considers it a blocker.
nickspiker commentedon Jun 23, 2026
Apologies, I replied to the stale unresolved questions and over focused on the intrinsic. Dropping that.
For me it's strictly a correctness issue. Every other float op propagates NaN, so an undefined result stays observable and you handle it once at the end.
min/maxare the lone exception that suppress it, which silently erases "this value is undefined" from a computation. I hit it as silent output corruption in an image pipeline: a NaN from an upstream error got absorbed into a plausible finite value instead of propagating, so the bug stayed invisible.The other thing that bugs me is that clamp ordering changes the result, but only on NaN.
(NaN).min(255.).max(0.) as u8gives 255, while(NaN).max(0.).min(255.) as u8gives 0. Identical for every finite value, both infinities, both signed zeros, yet opposite extremes (white vs black) for NaN, decided purely by which clamp came first. With propagatingminimum/maximumboth give 0, because NaN flows through and the saturating cast lands it at 0. That order independence is what I want, and it's the correct behavior.So +1 to @ickk: I specifically want the NaN propagating
minimum/maximum.RalfJung commentedon Jun 23, 2026
Note that we have
clampso you shouldn't have to use a min/max sequence. It has the NaN behavior you seem to be looking for.Propagating or not propagating NaN both can make sense, which is why the latest IEEE standard has both kinds of operations. I agree Rust should also have both. The main remaining blocker seems to be the naming -- but we'd need someone from @rust-lang/libs-api to chime in to be sure.