std::min(max, std::max(min, v));
maxsd xmm0, xmm1
minsd xmm0, xmm2
std::min(std::max(v, min), max); maxsd xmm1, xmm0
minsd xmm2, xmm1
movapd xmm0, xmm2
For min/max on x86 if any operand is NaN the instruction copies the second operand into the first. So the compiler can't reorder the second case to look like the first (to leave the result in xmm0 for the return value).The reason for this NaN behavior is that minsd is implemented to look like `(a < b) ? a : b`, where if any of a or b is NaN the condition is false, and the expression evaluates to b.
Possibly std::clamp has the comparisons ordered like the second case?
┌─/usr/include/c++/12/bits/stl_algo.h──────────────────────────────────────────────────────────────────────────────────────
│ 3617 \* @pre `_Tp` is LessThanComparable and `(__hi < __lo)` is false.
│ 3618 \*/
│ 3619 template<typename _Tp>
│ 3620 constexpr const _Tp&
│ 3621 clamp(const _Tp& __val, const _Tp& __lo, const _Tp& __hi)
│ 3622 {
│ 3623 __glibcxx_assert(!(__hi < __lo));
│ > 3624 return std::min(std::max(__val, __lo), __hi);
│ 3625 }
│ 3626> 2 Preconditions: `bool(comp(proj(hi), proj(lo)))` is false. For the first form, type `T` meets the Cpp17LessThanComparable requirements (Table 26).
> 3 Returns: `lo` if `bool(comp(proj(v), proj(lo)))` is true, `hi` if `bool(comp(proj(hi), proj(v)))` is true, otherwise `v`.
> 4 [Note: If NaN is avoided, `T` can be a floating-point type. — end note]
From Table 26:
> `<` is a strict weak ordering relation (25.8)
> 4 The term strict refers to the requirement of an irreflexive relation (`!comp(x, x)` for all `x`), and the term weak to requirements that are not as strong as those for a total ordering, but stronger than those for a partial ordering. If we define `equiv(a, b)` as `!comp(a, b) && !comp(b, a)`, then the requirements are that `comp` and `equiv` both be transitive relations:
> 4.1 `comp(a, b) && comp(b, c)` implies `comp(a, c)`
> 4.2 `equiv(a, b) && equiv(b, c)` implies `equiv(a, c)`
NaN breaks these relations, because `equiv(42.0, NaN)` and `equiv(NaN, 3.14)` are both true, which would imply `equiv(42.0, 3.14)` is also true. But clearly that's not true, so floating point numbers do not satisfy the strict weak ordering requirement.
The standard doesn't explicitly say that NaN is undefined behavior. But it does not define the behavior for when NaN is used with `std::clamp()`, which I think by definition means it's undefined behavior.
The various code snippets in the article don't compute the same "function". The order between the min() and max() matters even when done "by hand". This is apparent when min is greater than max as the results differ in the choice of the boundaries.
Funny that for such simple functions the discussion can become quickly so difficult/interesting.
Some toying around with the various implementations in C [1]: