收藏文章一篇(http://www.concentric.net/~Ttwang/tech/javafloat.htm)

Java Floating-Point Number Intricacies

Thomas Wang, March 2000last update September 2000

Abstract

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.

Introduction

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

Special Floating-Point Number Entities

Not A Number

"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

Operations involving 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

"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

Operations Involving Infinities

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

Negative Infinity

"-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

Operations Involving Negative 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

Negative Zero

"-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

Operations Involving Negative Zero

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 Java "Float" Objects

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

Some Actual Pitfalls

Wrong Way to Implement abs()

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.

Danger of Floating-Point Number Optimization

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.

Assuming A Floating-Point Value Is Equal To Itself

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.

Not Checking for NaN

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".

References

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)

你可能感兴趣的:(.net,F#)