Intel® C++ Compiler
Community support and assistance for creating C++ code that runs on platforms based on Intel® processors.

Inconsistent behavior in `std::min(-0.0f,+0.0f)`

Ian_Mallett
Beginner
444 Views

This case should be defined consistently. However, ICC (19.0.1) can be made to give inconsistent results. For example, consider the following code:

#include <algorithm>

inline float minwrap(float x, float y) { return std::min(x,y); }

void f(float);
void test() {
    f(std::min(-0.0f,+0.0f)); //Calls `f` with `-0.0f`
    f(minwrap(-0.0f,+0.0f));  //Calls `f` with `+0.0f`
}

As-suggested by the comments, simply wrapping a call to `std::min(...)` (in an `inline` function no less!) is sufficient to reverse which one is returned.

0 Kudos
4 Replies
jimdempseyatthecove
Honored Contributor III
444 Views

According to IEEE 754 -0.0 == 0.0, therefore when both arguments to std::min are "equal", either argument can be returned.

While you would like consistency, the inline minwrap is likely changing the register assignments, and as a result, changes the selection of which zero to use for function f.

If you really require consistency, then write you own minwrap that takes into account the possibility of signed zeros, and picks the one of interest.

Jim Dempsey

0 Kudos
Viet_H_Intel
Moderator
444 Views

Can you give us a complete test case which can be compiled and created an executable? and which compiler options to use?

0 Kudos
jimdempseyatthecove
Honored Contributor III
444 Views

BTW

I am not entirely sure if the C/C++ standard requires the literal expressed with -0.0 to generate/store an IEEE 754 0.0 with sign bit set. This may be an implementation issue that you best avoid.

Jim Dempsey

0 Kudos
Ian_Mallett
Beginner
444 Views

jimdempseyatthecove wrote:
According to IEEE 754 -0.0 == 0.0, therefore when both arguments to std::min are "equal", either argument can be returned.
jimdempseyatthecove wrote:
I am not entirely sure if the C/C++ standard requires the literal expressed with -0.0 to generate/store an IEEE 754 0.0 with sign bit set.
The underlying issue here is that IEEE 754 and C++ and different standards. AFAIK technically speaking the C++ standard does not require the use of IEEE 754 (or operations it defines), but since basically all floating-point implementations use IEEE 754 (the main exceptions being (1) flushing denormals, especially on Intel HW, (2) obscure architectures and domain-specific encodings, and (3) software implementations), in-practice, one can usually assume that C++ `float` and `double` are essentially IEEE 754-compliant.

You are correct that IEEE 754 does allow either argument to be returned for the analogous functions defined by that standard:

IEEE 754-2008 (§11) wrote:
Do not depend on the sign of a zero result [...] for minNum(x, y), maxNum(x, y), minNumMag(x, y), or maxNumMag(x, y) when x and y are equal.
However, this is not the C++ standard. The C++ standard says (of `std::min(...)`):
C++ N4810 Working Draft (§25.7.8) wrote:
Returns the first argument when the arguments are equivalent.
C++ N4810 Working Draft (§25.7) wrote:
The equivalence to which we refer is not necessarily an operator==, but an equivalence relation induced by the strict weak ordering. That is, two elements a and b are considered equivalent if and only if !(a < b) && !(b < a).
So AFAICT, since if both arguments are ±0, the comparisons are `false`, their complements, and subsequent conjunction, are `true`, and so the arguments are equivalent, and the first argument must be returned.

For reference, MSVC is also inconsistent, while Clang and GCC both seem to define it the way I described.

It's worth noting that NaN arguments can (and should) be carefully considered in this way as-well. Compilers do seem to be more-consistent for these cases, although while I'm here, I should mention that ICC (and others) regularly miss fairly simple optimization opportunities on that.

(Also, hopefully it's obvious, but all of this applies to `std::max(...)` analogously as-well.)

jimdempseyatthecove wrote:
While you would like consistency, the inline minwrap is likely changing the register assignments, and as a result, changes the selection of which zero to use for function f.
That's my guess as-well. Happily, the above matches quite conveniently to the ASM, since `v?min[sp][ds]` returns `SRC2` if both arguments are ±0 or if either is NaN. So, pass them in the opposite order as for `std::min(...)` and there should be no performance penalty.

Viet Hoang (Intel) wrote:
Can you give us a complete test case which can be compiled and created an executable? and which compiler options to use?
Adapting the example I gave above is almost trivial, but here you go:

#include <algorithm>
#include <cstdio>

inline float minwrap(float x, float y) { return std::min(x,y); }

void f(float x) { printf("%f\n",(double)x); }
int main(int,char*[]) {
    f(std::min(-0.0f,+0.0f)); //Calls `f` with `-0.0f`
    f(minwrap (-0.0f,+0.0f)); //Calls `f` with `+0.0f`
    return 0;
}

Compile it with "-O3".

Best,
Ian

0 Kudos
Reply