The semantic of Java floating point number generally follows the IEEE 754 Binary Floating-Point Arithmetic standard. The IEEE 754 standard provides for the definitions of floating-point infinities, negative infinities, negative zero, and NaN (not a number). In the context of Java, these entities are often confusing to typical programmers.
In this article, we will discuss the results of computing with these special floating-point entities. We will also point out some common Java floating-point number pit falls.
The Java programming language provides two built-in classes for representing floating-point numbers: float, and double. The "float" class takes 4 bytes of storage, and have 23 binary digits of precision. The "double" class takes 8 bytes of storage, and have 52 binary digits of precision.
3.0d is a double precision floating-point number constant. 3.0, or alternatively 3.0f is a single precision floating-point number constant.
float f = 3.0f;
double d = 3.0d;
The Java core library also provides two wrapped classes java.lang.Float, and java.lang.Double. These two classes allow floating-point objects to be stored in Java collection objects, such as hash tables. These two classes also provides parsing, and conversion helper methods.
Float ff = new Float(1.0f); // creates a Java "Float" object
Double dd = new Double(2.0d); // creates a Java "Double" object
"NaN" stands for "not a number". "Nan" is produced if a floating point operation has some input parameters that cause the operation to produce some undefined result. For example, 0.0 divided by 0.0 is arithmetically undefined. Taking the square root of a negative number is also undefined.
0.0 / 0.0 -> NaN
Math.sqrt(-2.0) -> NaN
Double.NaN + Double.NaN -> NaN
Float.NaN + 2.0 -> NaN
Float.NaN * 3.0 -> NaN
(0.0 / 0.0) * Float.POSITIVE_INFINITY -> NaN
Math.abs(0.0 / 0.0) -> NaN
(int) (Double.NaN) -> 0
All boolean operations involving "NaN" results in a false value.
Double.NaN > 1.0 -> false
Double.NaN < 1.0 -> false
Double.NaN == 1.0 -> false
Float.NaN < -3.0 -> false
Float.NaN > Float.POSITIVE_INFINITY -> false
Float.NaN < Float.POSITIVE_INFINITY -> false
(0.0 / 0.0) == (0.0 / 0.0) -> false
"Infinity" is produced if a floating point operation creates such a large floating-point number that it cannot be represented normally. "Infinity" is a special value that represent the concept of positive infinity.
1.0 / 0.0 -> Infinity
Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY -> true
Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY -> true
(1.0 / 0.0) + 2.0 -> Infinity
(1.0 / 0.0) * 0.5 -> Infinity
(1.0 / 0.0) * 0.0 -> NaN
(1.0 / 0.0) + (1.0 / 0.0) -> Infinity
(1.0 / 0.0) * (1.0 / 0.0) -> Infinity
(1.0 / 0.0) - (1.0 / 0.0) -> NaN
(1.0 / 0.0) / (1.0 / 0.0) -> NaN
(int) (1.0 / 0.0) -> 2147483647
"-Infinity" is produced if a floating point operation creates such an extremely negative floating-point number that it cannot be represented normally. "-Infinity" is a special value that represent the concept of negative infinity.
-1.0 / 0.0 -> -Infinity
- Double.POSITIVE_INFINITY == Doublle.NEGATIVE_INFINITY -> true
Float.NEGATIVE_INFINITY < 1.0 -> true
(-1.0 / 0.0 ) + 2.0 -> -Infinity
(-1.0 / 0.0) == (-1.0 / 0.0) -> true
(-1.0 / 0.0) - (-1.0 / 0.0) -> NaN
(-1.0 / 0.0) * 0.5 -> -Infinity
(-1.0 / 0.0) * 0.0 -> NaN
Math.abs(-1.0 / 0.0) -> Infinity
(int) (-1.0 / 0.0) -> -2147483648
"-0.0" is produced when a floating-point operation results in a negative floating-point number so close to 0 that cannot be represented normally.
-2.0 / Float.POSITIVE_INFINITY -> -0.0
"-0.0" is numerically identical to "0.0". However, some operations involving "-0.0" are different than the same operation with "0.0".
(-0.0) == 0.0 -> true
2.0 / (0.0) -> Infinity
2.0 / (-0.0) -> -Infinity
0.0 > (-0.0) -> false
(-0.0) * (-0.0) -> 0.0
3.0 + (-0.0) -> 3.0
4.0 * (-0.0) -> -0.0
0.0 - 0.0 -> 0.0
(-0.0) - (-0.0) -> 0.0
(-0.0) + (-0.0) -> -0.0
(-0.0) + 0.0 -> 0.0
0.0 + (-0.0) -> 0.0
(-0.0) - 0.0 -> -0.0
- (-0.0) -> 0.0
- (0.0) -> -0.0
0.0 / (-0.0) -> NaN
Comparing two Java "Float" objects can have different semantics than comparing two Java "float" values. Recall the "float" class is a Java primitive class, while java.lang.Float is a subclass of "Object".
A "NaN" value is not equal to itself. However, a "NaN" Java "Float" object is equal to itself. The semantic is defined this way, because otherwise "NaN" Java "Float" objects cannot be retrieved from a hash table.
(new Float(0.0 / 0.0)).equals(new Float(0.0 / 0.0)) -> true
For the class java.lang.Float, objects are ordered from lowest to highest: -Infinity, negative numbers, -0.0, 0.0, positive numbers, Infinity, NaN. "java.lang.Double" objects are identically ordered.
(new Float(0.0)).equals(new Float(-0.0)) -> false
(new Float(-1.0 / 0.0)).compareTo(new Float(-3.0)) -> -1
(new Float(-3.0)).compareTo(new Float(-0.0)) -> -1
(new Float(-0.0)).compareTo(new Float(0.0)) -> -1
(new Float(0.0)).compareTo(new Float(3.0)) -> -1
(new Float(3.0)).compareTo(new Float(1.0 / 0.0)) -> -1
(new Float(1.0 / 0.0)).compareTo(new Float(0.0 / 0.0)) -> -1
This is the wrong way:
(f >= 0.0) ? f : - f
What if 'f' is -0.0? Since 0.0 is equal to -0.0, the above expression would return the incorrect value of -0.0, versus the correct value of 0.0.
The correct expression is:
(f <= 0.0) ? 0.0 - f : f
Be very careful when you are optimizing a floating point expression. For example, if you optimized the above expression to:
(f <= 0.0) ? - f : f
Then you introduced a bug that causes abs(0.0) to return -0.0.
We have already seen a case where (0.0 - f) is not equal to (- f).
! (a < b) does not imply that (a >= b).
! (1.0 < NaN) -> true
(1.0 >= NaN) -> false
One page 308 of the book "The Java Language Specification", it talks in more detail of the danger of optimizing floating-point expressions.
Well, a "NaN" value is not equal to itself. For similar reason, assuming a number subtracting itself must equal to 0.0 is wrong. "NaN" - "NaN" still yields "NaN". "Infinity" - "Infinity" yields "NaN", although two Infinities are equal to each other.
public employee(float monthly_salary)
{
if (monthly_salary < 0.0)
throw IllegalArgumentException("illegal monthly salary");
this.yearly_salary = 12.0 * monthly_salary;
}
The above piece of code would not catch the mistake of using a "NaN" as an input argument. The code below would catch it.
public employee(float monthly_salary)
{
if (monthly_salary >= 0.0)
{
this.yearly_salary = 12.0 * monthly_salary;
}
else throw IllegalArgumentException("illegal monthly salary");
}
Looking at the fixed code, what should we do if the input argument is "Infinity"? Depending on the API's specification, we can do a number of different things. The point is that any time we are dealing with floating-point numbers, we should think about the cases dealing with "NaN", "Infinity", "-Infinity", and "-0.0".
James Gosling, Bill Joy, Guy Steele, "The Java Language Specification", Addison Wesley, (1996)
IEEE, "ANSI/IEEE Std 754-1985, An American National Standard IEEE Standard for Binary Floating-Point Arithmetic", (1985)