The implementation of Min and Max operations for floating-point types

Story about differences in software implementations of simple mathematical operations answering the question: “Which of two floating-point values are greater” (or lower).

The float-related implementations of Min and Max are different and in most cases their behavior is not conforming to
IEEE-754–2008 standard (IEEE Standard for Binary Floating-Point Arithmetic).

Consider the following example of naïve generic Max JavaScript implementation:

function Max(a, b) {
if(a > b) {
return a;
} else {
return b;
}
}

This implementation isn’t correct in many ways:

  • It cannot handle -0 +0 cases (+0 should be greater than -0);
  • It doesn’t handles input and number of arguments according to JavaScript specification (-Infinity / +Infinity cases)

And most interesting:

  • It cannot handle NaN value correctly (Not-a-Number)
My favorite NaN source: zero by zero division

But what is the correct way of handling NaN situation, when a is NaN or b is NaN, or both a and b is NaN. As soon as wee use floating-point numbers we can find citations of IEEE standard and then we will find definitions of minNum and maxNum operations,
which prefer numbers over NaN values, so Max(NaN, number) = Max(number, NaN) = number.

But there are not so many modern IEEE-compatible implementations.

IEEE 754–2008 compatible NaN handling libraries,

where Max(NaN, number) = Max(number, NaN) = number:
C language, since C99 http://en.cppreference.com/w/c/numeric/math/fmax

IEEE 754–2008 incompatible NaN handling libraries,

where Max(NaN, number) = Max(number, NaN) = NaN:

Examples of libraries throwing exceptions in case of NaN arguments:

  • Ruby (there are max/min methods defined on Enumerable, no standard Max/Min implementations),
    will throw “comparison of Float with NaN failed”. But NaN class is still float. Older version of ruby interpreter can throw “comparison of Float with Float failed” exception.

Non-commutative behavior

where Max(number, NaN) isn’t equal to Max(NaN, number)

Swift, Haskell, OCaml

Workarounds available.

Conclusion

It seems that most of the time we should ensure that all of arguments of Min/Max functions are not NaNs, because results can be spoiled by other argument depending on Min/Max implementation.
IEEE standard defines standard behavior, but most of the time in modern languages you will see another behavior.
Why?

  • It seems that is because there are no other way of getting error source in result, except exception. And exceptions are considered slow;
  • Engineers are making standard libraries by looking at other standard libraries implementations;
  • Standard pdf itself isn’t available for free.
  • Generic one-for-all implementation looks attractive.
  • This is a corner case which is not important most of the time for developers. We should just sit and wait until the problem strikes.

p.s. There is also (± Infinity, NaN) corner case.

Although Standard entries about float operations are available, it’s better for you to read [your_favorite_programming_language] language specification first.