We're looking at the behavior of Math.abs(x) where x is a NaN which is not the canonical Double.NaN. There are three plausible behaviors:
1. return x unchanged
2. set only the IEEE "sign bit" of the NaN to zero, leaving other bits undisturbed
3. return Double.NaN ("collapse" all NaNs to one value)
The various specs and implementations in the JDK do all three!
"If the argument is not negative, the argument is returned."
implies 1.
* In other words, the result is the same as the value of the expression:
* <p>{@code Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)}
implies 3. The spec seems self-contradictory here.
Did the spec writer intend doubleToRawLongBits instead of doubleToLongBits, which would be 2.?
The java code in the method
return (a <= 0.0D) ? 0.0D - a : a;
implies 1.
But if you actually test the implementation, you get intrinsified hotspot behavior 2.
The statement
"If the argument is NaN, the result is NaN."
is insufficiently clear - is it referring to _a_ NaN or _the_ NaN ?
Is it saying that the range of the function when restricted to the set of NaNs is the set of NaNs?
The good news is that the hotspot implementation that users see in practice is what we want. We want to set the sign bit to zero, and do nothing else, even for NaNs. This preserves any "payload" stored in the NaN and is in compliance with IEEE 754 5.5.1 . """To facilitate propagation of diagnostic information contained in NaNs, as much of that information as possible should be preserved in NaN results of operations""" Java should also strive to not collapse NaNs.
So we should change the spec and java body for Math.abs(double) (and of course StrictMath, and float) to comply with the hotspot intrinsification.
The sample program below demonstrates the problem, and also shows that behavior has changed between jdk7 and jdk8:
jdk7:
Math.abs: flips sign bit
StrictMath.abs: value unchanged
abs2: value unchanged
abs3: collapses to Double.NaN
abs4: flips sign bit
jdk9:
Math.abs: flips sign bit
StrictMath.abs: flips sign bit
abs2: value unchanged
abs3: collapses to Double.NaN
abs4: flips sign bit
abs4 is a fine replacement for the java body.
/** Demonstrates results of variants of abs(a negative NaN) */
public class AbsNaN {
static double abs2(double a) {
return (a <= 0.0D) ? 0.0D - a : a;
}
static double abs3(double a) {
return Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1);
}
static double abs4(double a) {
return Double.longBitsToDouble((Double.doubleToRawLongBits(a)<<1)>>>1);
}
/** A NaN with a "payload". */
static final double posNaN = Double.longBitsToDouble(0x7ff8000000000F00L);
static final double negNaN = flipSignBit(posNaN);
static void printAction(String what, double a) {
assert Double.isNaN(a);
System.out.printf(
"%s: %s%n", what,
bitEquals(a, negNaN) ? "value unchanged" :
bitEquals(a, posNaN) ? "flips sign bit" :
bitEquals(a, Double.NaN) ? "collapses to Double.NaN" :
"Huh?");
}
public static void main(String[] args) throws Throwable {
assert Double.isNaN(posNaN);
assert Double.isNaN(negNaN);
printAction("Math.abs", Math.abs(negNaN));
printAction("StrictMath.abs", StrictMath.abs(negNaN));
printAction("abs2", abs2(negNaN));
printAction("abs3", abs3(negNaN));
printAction("abs4", abs4(negNaN));
}
static double flipSignBit(double a) {
return Double.longBitsToDouble(Double.doubleToRawLongBits(a) ^ (1L << 63));
}
static boolean bitEquals(double a, double b) {
return Double.doubleToRawLongBits(a) == Double.doubleToRawLongBits(b);
}
}
1. return x unchanged
2. set only the IEEE "sign bit" of the NaN to zero, leaving other bits undisturbed
3. return Double.NaN ("collapse" all NaNs to one value)
The various specs and implementations in the JDK do all three!
"If the argument is not negative, the argument is returned."
implies 1.
* In other words, the result is the same as the value of the expression:
* <p>{@code Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)}
implies 3. The spec seems self-contradictory here.
Did the spec writer intend doubleToRawLongBits instead of doubleToLongBits, which would be 2.?
The java code in the method
return (a <= 0.0D) ? 0.0D - a : a;
implies 1.
But if you actually test the implementation, you get intrinsified hotspot behavior 2.
The statement
"If the argument is NaN, the result is NaN."
is insufficiently clear - is it referring to _a_ NaN or _the_ NaN ?
Is it saying that the range of the function when restricted to the set of NaNs is the set of NaNs?
The good news is that the hotspot implementation that users see in practice is what we want. We want to set the sign bit to zero, and do nothing else, even for NaNs. This preserves any "payload" stored in the NaN and is in compliance with IEEE 754 5.5.1 . """To facilitate propagation of diagnostic information contained in NaNs, as much of that information as possible should be preserved in NaN results of operations""" Java should also strive to not collapse NaNs.
So we should change the spec and java body for Math.abs(double) (and of course StrictMath, and float) to comply with the hotspot intrinsification.
The sample program below demonstrates the problem, and also shows that behavior has changed between jdk7 and jdk8:
jdk7:
Math.abs: flips sign bit
StrictMath.abs: value unchanged
abs2: value unchanged
abs3: collapses to Double.NaN
abs4: flips sign bit
jdk9:
Math.abs: flips sign bit
StrictMath.abs: flips sign bit
abs2: value unchanged
abs3: collapses to Double.NaN
abs4: flips sign bit
abs4 is a fine replacement for the java body.
/** Demonstrates results of variants of abs(a negative NaN) */
public class AbsNaN {
static double abs2(double a) {
return (a <= 0.0D) ? 0.0D - a : a;
}
static double abs3(double a) {
return Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1);
}
static double abs4(double a) {
return Double.longBitsToDouble((Double.doubleToRawLongBits(a)<<1)>>>1);
}
/** A NaN with a "payload". */
static final double posNaN = Double.longBitsToDouble(0x7ff8000000000F00L);
static final double negNaN = flipSignBit(posNaN);
static void printAction(String what, double a) {
assert Double.isNaN(a);
System.out.printf(
"%s: %s%n", what,
bitEquals(a, negNaN) ? "value unchanged" :
bitEquals(a, posNaN) ? "flips sign bit" :
bitEquals(a, Double.NaN) ? "collapses to Double.NaN" :
"Huh?");
}
public static void main(String[] args) throws Throwable {
assert Double.isNaN(posNaN);
assert Double.isNaN(negNaN);
printAction("Math.abs", Math.abs(negNaN));
printAction("StrictMath.abs", StrictMath.abs(negNaN));
printAction("abs2", abs2(negNaN));
printAction("abs3", abs3(negNaN));
printAction("abs4", abs4(negNaN));
}
static double flipSignBit(double a) {
return Double.longBitsToDouble(Double.doubleToRawLongBits(a) ^ (1L << 63));
}
static boolean bitEquals(double a, double b) {
return Double.doubleToRawLongBits(a) == Double.doubleToRawLongBits(b);
}
}
- relates to
-
JDK-8164524 Correct inconsistencies in floating-point abs spec
-
- Closed
-