Why is Java's double/float Math.min() implemented this way?

JavaFloating Point

Java Problem Overview


I was looking through some things in the source of java.lang.Math, and I noticed that while Math.min(int, int) (or its long counterpart) is implemented this way:

public static int min(int a, int b) {
   return a <= b ? a : b;
}

And this makes complete sense to me and it is the same as what I would do. However, the double/float implementation is this:

public static float min(float a, float b) {
   if (a != a) {
      return a;
   } else if (a == 0.0F && b == 0.0F && (long)Float.floatToRawIntBits(b) == negativeZeroFloatBits) {
      return b;
   } else {
      return a <= b ? a : b;
   }
}

I'm completely dumbfounded. Comparing a to itself? What's the second check even for? Why isn't it implemented in the same way as the int/long version?

Java Solutions


Solution 1 - Java

Floating-point numbers are way more complicated than integer values.

For this specific case two distinctions are important:

  • NaN is a valid value for float and double which represents "not a number" and behaves weirdly. Namely, it doesn't compare equal to itself.
  • Floating point numbers can differentiate between 0.0 and -0.0. A negative zero could conceivably be useful when you're calculating the limit of some function. Distinguishing whether a limit approaches 0 from the positive or the negative direction could be beneficial.

So this part:

if (a != a) {
      return a;
}

ensures that NaN is returned if a is NaN (if a is not NaN, but b is, then the "normal" check later on will return b, i.e. NaN, so no explicit check is needed for this case). This is a common pattern: when calculating anything where one input is NaN, the output will also be NaN. Since NaN usually represents some error in the calculation (such as dividing 0 by 0), it's important that it "poisons" all further calculations to ensure the error isn't silently swallowed.

This part:

if (a == 0.0F && b == 0.0F && (long)Float.floatToRawIntBits(b) == negativeZeroFloatBits) {
      return b;
}

ensures that if you compare two zero-valued floating point numbers and b is negative zero then that negative zero is returned (since -0.0 is "smaller" than 0.0). Similarly to NaN the normal check will correctly return a if it's -0.0 and b is 0.0.

Solution 2 - Java

I recommend carefully reading the documentation for Math.min and also the numeric comparison operators on floating points. Their behaviours are quite different.

Relevant parts from Math.min:

> If either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this method considers negative zero to be strictly smaller than positive zero.

and from JLS §15.20.1 "Numerical Comparison Operators <, <=, >, and >="

> The result of a floating-point comparison, as determined by the specification of the IEEE 754 standard, is: > > - If either operand is NaN, then the result is false. > > - Positive zero and negative zero are considered equal.

If any argument is NaN, Math.min picks that one, but if any operand is NaN, <= evaluates to false. This is why it has to check if a not equal to itself - this would mean a is NaN. If a is not NaN but b is, the last case would cover it.

Math.min also considers -0.0 to be "less than" +0.0, but the numeric comparison operators think they are equal. This is the purpose of the second check.

Solution 3 - Java

Just for completeness/clarity, let's draw up a table of all possible outcomes:

  • Either of a and b can be either

    • NaN,
    • −0,
    • 0 (i.e. +0), or
    • some other non-NaN non-zero value, marked as "(other)".

    Writing out all combinations of these for completeness, and distinguishing between positive and negative numbers for clarity in some cases, gives the 20 rows in the table below, though most of them are straightforward and unproblematic.

  • The column titled "Correct min" is the correct value that is supposed to be returned according to the IEEE 754 standard and Java documentation of Math.min, and the column titled "Naive min" is the value that would have been returned if Math.min had been implemented as return a <= b ? a : b; instead.

a b Correct min Naive min Notes on naive min Naive min wrong?
NaN NaN NaN NaN b, as NaN comparison gives false.
NaN −0 NaN −0 b, as NaN comparison gives false. Wrong
NaN 0 NaN 0 b, as NaN comparison gives false. Wrong
NaN (other) NaN (other) b, as NaN comparison gives false. Wrong
−0 NaN NaN NaN b, as NaN comparison gives false.
−0 −0 −0 −0 a, as −0 ≤ −0.
−0 0 −0 −0 a, as −0 ≤ 0.
−0 (other>0) −0 −0
−0 (other<0) (other<0) (other<0)
0 NaN NaN NaN b, as NaN comparison gives false.
0 −0 −0 0 a, as "0 ≤ −0" per IEEE 754. Wrong
0 0 0 0 a, as 0 ≤ 0.
0 (other>0) 0 0
0 (other<0) (other<0) (other<0)
(other) NaN NaN NaN b, as NaN comparison gives false.
(other<0) −0 (other<0) (other<0)
(other>0) −0 −0 −0
(other<0) 0 (other<0) (other<0)
(other>0) 0 0 0
(other) (other) (other) (other)

[The "(other)" in the last row for "Correct min" and "Naive min" means the correct minimum, in the straightforward sense without any confusion because of NaN or −0.]

So you see there are four rows in the table above in which the naive function would give a wrong answer:

  • three of them are the case when a is NaN, but b is not. This is what the first check in the function is for.

  • the other is the case where Math.min(0, -0) is documented by Java as returning −0, even though IEEE 754 treats 0 and −0 as equal for comparison (and therefore the comparison "0 ≤ −0" evaluates as true). This is what the second check in the function is for.

Solution 4 - Java

I can help you on the first comparison if (a != a). This obviously only looks at a, so in which cases might a be the minimum regardless of b?

float numbers differ from int by having special values, for example NAN. And one special property of NAN is that a comparison is always false. So the first condition returns a if every comparison operator returns false on a.

The same condition for b can be found in the last line. If a comparison on b always returns false, the last line always returns b.

On the second condition I can only guess that this is related to "negative zero" and "positive zero", another two special values of float. And of course, a negative zero is smaller than a positive zero.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYonatan AvharView Question on Stackoverflow
Solution 1 - JavaJoachim SauerView Answer on Stackoverflow
Solution 2 - JavaSweeperView Answer on Stackoverflow
Solution 3 - JavaShreevatsaRView Answer on Stackoverflow
Solution 4 - JavaMathiasView Answer on Stackoverflow