Skip to content

Tracking Issue for float_minimum_maximum #91079

Open
1 of 1 issue completed
Open

Description

@Urgau

View all comments

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

Unresolved Questions

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    T-libs-api[DEPRECATED; DO NOT USE]
    on Nov 20, 2021
  2. CryZe commented on Nov 21, 2021

    @CryZe
    Contributor

    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

  3. workingjubilee commented on Nov 21, 2021

    @workingjubilee
    Member

    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.

  4. scottmcm commented on Nov 22, 2021

    @scottmcm
    Member

    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.

  5. workingjubilee commented on Nov 26, 2021

    @workingjubilee
    Member

    Ah, Damn.

  6. rben01 commented on May 15, 2022

    @rben01

    Everything that follows will be stated in terms of the min and minimum functions but applies equally to max and maximum.

    Does the function have to be called minimum? I know that's the IEEE name but I think I'd find having both min and minimum confusing, as their names don't really say which is which. "The function that requires both inputs be NaN for its output to be NaN is the one with the shorter name" is not a natural rule to remember IMO. I think I'd prefer having minimum be called (say) partial_min to suggest that it follow the same rules as PartialEq and PartialOrd, which is that a single NaN in the input is sufficient to produce a missing output.

  7. m-ou-se commented on Aug 16, 2022

    @m-ou-se
    Member

    Nominated at the request of @Urgau.

  8. m-ou-se commented on Aug 23, 2022

    @m-ou-se
    Member

    The confusion between maximum vs max is definitely a problem, so we'll need to do something about that before we can stabilize that. The max and min functions 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 max vs maximum?

  9. alistaircarscadden commented on Aug 25, 2022

    @alistaircarscadden

    I agree that having pairs of functions such that min != minimum and max != maximum is 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 think minimum / maximum is correct.

    Here are some suggestions to add to the brainstorm. Any of these can be min or minimum and apply to both min and max.

    • min_nan,
    • min_propigate_nans
    • min_any_nan
    • min_ieee_754_2019
    • min_nans_win
    • etc.

    I think that min with some suffix is preferable to minimum because it shows it is an alteration of the existing min function, not a competing function. I don't know the whole history of relevance of IEEE 754 2019 but I think choosing minimum because that's how it is named in that standard is not clear to the average Rust programmer.

  10. rben01 commented on Aug 25, 2022

    @rben01

    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.

  11. CryZe commented on Aug 25, 2022

    @CryZe
    Contributor

    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)

  12. scottmcm commented on Aug 25, 2022

    @scottmcm
    Member

    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 add and atan2 do the same "NAN if either input is NAN", same as what maximum does.

    So one option might be to deprecate max under that name and offer it as max_ignoring_nans or something, letting the new one get the name maximum.

    (Thought that does have the obvious downside of being different from the max method on everything that's Ord.)

  13. Urgau commented on Aug 25, 2022

    @Urgau
    MemberAuthor

    How do other languages and libraries handle this?

    C/C++/Swift doesn't (yet ?). C#/Python have the IEEE 754-2019 minimum and maximum semantics as their min/max function from the start. BUT there doesn't seems to have any language that have both the 2008 and 2019 semantics.

    How are these named in the standard?

    They are named minimum and maximum in IEEE 754-2019.
    Note that the previous functions minNum and maxNum from IEEE 754-2008 (and below) are deprecated in IEEE 754-2019 in favor of the new one.

    What are the use cases for max vs maximum?

    (I don't really understand the question) but I would say none. Only one should exist, in IEEE 754-2019 they deprecated minNum and maxNum in favor of minimum and `maximum, maybe we should do the same.

    So one option might be to deprecate max under that name and offer it as max_ignoring_nans or something, letting the new one get the name maximum.

    +1


    Let me know if you need more input. Happy to help.

  14. 30 remaining items

  15. valadaptive commented on Nov 30, 2025

    @valadaptive
    Contributor

    Two clarify: you would prefer having two functions that return the non-NAN (YAN?) functions but differ only by their handling of signed zeroes? Current min is 2008 minNum.

    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.

  16. valadaptive commented on Dec 1, 2025

    @valadaptive
    Contributor

    To get an idea of what exactly we're looking at optimization-wise, here's a table of how these scalar ops currently get lowered. Based on https://llvm.godbolt.org/z/f3n57WE1q.
    [snip]

    The aarch64 lowerings of minimumNumber use 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 minNum lowering, 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 min and max--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?

  17. RalfJung commented on Dec 1, 2025

    @RalfJung
    Member

    @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 our min to do.

    EDIT: Ah, I think that is what @valadaptive also referred to, except for the context of minimumNumber.

  18. ickk commented on Feb 1, 2026

    @ickk
    Contributor

    I agree that having three operations is confusing, and we should stick to two. I'd prefer minimumNumber and maximumNumber for the reasons I gave above, as well as the current min/max which don't require expensive emulation on x86. Since the specifics of NaN bit patterns don't apply here due to LLVM semantics, the only difference between the operations would be signed zero handling.

    On the contrary I would really like to have some way to get the IEEE754-2019 minimum behaviour (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) and minimumNumber(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 both min(2008) and minimumNumber(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, and
    • relaxed-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 cfg to 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 to std::arch one way or another, rather than having Rust choose a favourite arch at the expense of useful semantics.

  19. Apersoma commented on Mar 17, 2026

    @Apersoma
    Contributor

    this should probably change the deprecation message of abs_sub to use maximum instead of max because it correctly propagates nan.

  20. nickspiker commented on Jun 23, 2026

    @nickspiker

    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 (fminnm on aarch64). So:

    f32::min (suppressing) lowers to one instruction class:

    old_min:
        fminnm s1, s1, s1
        fminnm s0, s0, s0
        fminnm s0, s0, s1
        ret

    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):

    new_minimum:
        fcmp s0, s1
        b.mi .LBB0_5
        fcmp s1, s0
        b.pl .LBB0_3
        fmov s0, s1
        ret
        ...

    The downstream cost: that open-coded blob also defeats the fptoui.sat-of-clamp peephole. x.maximum(0.0).minimum(255.0) as u8 is provably identical to x as u8 for 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. With llvm.minimum/llvm.maximum intrinsics, 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.

  21. RalfJung commented on Jun 23, 2026

    @RalfJung
    Member

    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.

  22. nickspiker commented on Jun 23, 2026

    @nickspiker

    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/max are 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 u8 gives 255, while (NaN).max(0.).min(255.) as u8 gives 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 propagating minimum/maximum both 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.

  23. RalfJung commented on Jun 23, 2026

    @RalfJung
    Member

    Note that we have clamp so 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.

  24. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-floating-pointArea: Floating point numbers and arithmeticC-tracking-issueCategory: 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.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions