Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 665
Yonatan Avhar Avatar asked May 06 '21 07:05

Yonatan Avhar


People also ask

How does the math MIN function work?

min(int a, int b) returns the smaller of two int values. That is, the result is the value closer to negative infinity. If the arguments have the same value, the result is that same value.

What is double precision floating point in Java?

The float and double primitive types in Java are floating point numbers, where the number is stored as a binary representation of a fraction and a exponent. More specifically, a double-precision floating point value such as the double type is a 64-bit value, where: 1 bit denotes the sign (positive or negative).

How many arguments does MIN and MAX function math objects take?

math. min() is an inbuilt method in Java which is used to return Minimum or Lowest value from the given two arguments. The arguments are taken in int, float, double and long.

Why is double not accurate?

doubles are not exact. It is because there are infinite possible real numbers and only finite number of bits to represent these numbers.


2 Answers

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.

like image 90
Joachim Sauer Avatar answered Oct 19 '22 20:10

Joachim Sauer


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.

like image 40
Sweeper Avatar answered Oct 19 '22 19:10

Sweeper