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?
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.
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).
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.
doubles are not exact. It is because there are infinite possible real numbers and only finite number of bits to represent these numbers.
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.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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With