|
Welcome to the Core JavaTM
Technologies Tech Tips, February 4, 2003. Here you'll get tips
on using core Java technologies and APIs, such as those in
Java 2 Platform, Standard Edition (J2SETM).
This issue covers:
Some Things You Should Know About Floating-Point
Arithmetic
These tips were developed using Java 2 SDK, Standard
Edition, v 1.4.
This issue of the Core Java Technologies Tech Tips is
written by Glen McCluskey.
SOME THINGS YOU SHOULD KNOW ABOUT FLOATING-POINT
ARITHMETIC
Suppose that you are doing some Java programming involving
floating-point calculations. Suppose too that your code takes
the value 0.1 and adds it to itself a couple of times, like
this: public class Fp1 {
public static void main(String[] args) {
double val1 = 0.1;
double val2 = 0.3;
double d = val1 + val1 + val1;
if (d != val2) {
System.out.println("d != val2");
}
System.out.println(
"d - val2 = " + (d - val2));
}
}
Everyone knows that: 0.1 + 0.1 + 0.1 == 0.3
at least in mathematical terms. But when you run the
Fp1 program, you discover that this equality no
longer holds. The program output looks like this: d != val2
d - val2 = 5.551115123125783E-17
This result seems to indicate that 0.1 + 0.1 + 0.1 differs
from 0.3 by about 5.55e-17.
What is wrong here? The problem is that IEEE 754
floating-point arithmetic, as found in the Java language,
doesn't use decimals to represent numbers. Instead it uses
binary fractions and exponents. To understand what this means
in practice, consider trying to write various decimal
fractions as the sum of a series of powers of two: 0.5 = 1/2
0.75 = 1/2 + 1/4
0.875 = 1/2 + 1/4 + 1/8
0.1 = 1/16 + 1/32 + 1/256 + 1/512 + 1/4096 + 1/8192 + ...
The last series is infinite, which means that 0.1 is not
exactly representable in floating-point format. The infinite
series must be cut off at some point, rounded, and so on, in
order to be represented in a 64-bit double value. This process
results in some amount of error.
There are several ways to fix the equality testing problem
illustrated above. You could check that d and
val2 are close in value rather than exactly
equal. Or you could use the BigDecimal class, and
opt out of floating-point arithmetic entirely.
Here's another example where floating-point works
differently from what you'd expect when applying math rules:
public class Fp2 {
public static void main(String[] args) {
double val1 = 12345.0;
double val2 = 1e-16;
if (val1 + val2 == val1) {
System.out.println(
"val1 + val2 == val1");
}
}
}
The result of the Fp2 program is: val1 + val2 == val1
In mathematical terms, if: a > 0 && b > 0
then: a + b != a
But this program demonstrates behavior that violates this
rule. The problem is with the precision of floating-point
values. The precision is the actual number of bits used to
represent the value (the binary fraction), and is distinct
from the range of values that can be expressed using
floating-point. For Java double values, there are 53 bits of
precision, or about 16 decimal digits. The example above
implies a precision of approximately 20 digits, since the
addition can be rewritten as: 1.2345e+4 + 1e-16
Because the precision of double values is not sufficient to
represent the result of this addition, the 1e-16 is
effectively ignored.
Here's another example that illustrates how the laws of
math are violated: public class Fp3 {
public static void main(String[] args) {
double d1 = 1.6e308;
double d2 = d1 * 2.0 / 2.0;
System.out.println(d1);
System.out.println(d2);
}
}
If d1 is multiplied and then divided by 2.0,
the result should be d1, right? Unfortunately
not. The result is actually this: 1.6E308
Infinity
The initial multiplication d1 * 2.0 overflows,
and the division by 2.0 comes too late to do any good. The
maximum double value is approximately 1.8e308. Multiplying
1.6e308 by 2.0 results in 3.2e308, which becomes positive
infinity.
Let's look at another facet of floating-point arithmetic.
If you've used integer arithmetic much, you know that division
by zero triggers an ArithmeticException. But what
about similar usage with floating-point? Here's an example:
public class Fp4 {
public static void main(String[] args) {
System.out.println(1.0 / -0.0);
System.out.println(1.0 / 0.0);
System.out.println(0.0 / 0.0);
}
}
When you run this program, the output is: -Infinity
Infinity
NaN
The program throws no exceptions for floating-point
division by zero. When a positive or negative value is divided
by zero, the result is an infinity.
This example shows that there is, in fact, more than one
zero value. There are negative and positive zeros. The sign of
the zero is taken into account in deciding whether the result
of the division is negative or positive infinity.
What about the third division, 0.0 / 0.0? The
result of this operation is the value NaN (not a number).
Negative and positive zeros and NaN have some odd
properties. Let's look at another example to help clarify
this: public class Fp5 {
public static void main(String[] args) {
double d1 = -0.0;
double d2 = +0.0;
if (d1 < d2) {
System.out.println("-0.0 < +0.0");
}
double d3 = 0.0 / 0.0;
if (d3 == d3) {
System.out.println("d3 == d3");
}
}
}
If you run the Fp5 program, you'll see that it
prints nothing. As you saw in the previous example, the sign
of a zero is propagated, for example 1.0 / -0.0 == -Infinity
and 1.0 / +0.0 == +Infinity. But that's not the same as saying
that -0.0 < +0.0, as the example above demonstrates (d1 is
not less than d2, and so nothing is printed). Whether -0.0
comes "before" +0.0 depends on your perspective.
The Fp5 program also illustrates the point
that NaN values are unordered, so that d3 is not equal to
itself. This is the way that you write a method to tell
whether a value is NaN. If the value is not equal
to itself, then it is NaN. Such methods actually
already exist -- see the Float.isNan and
Double.isNaN methods.
Let's look at a final example that illustrates the ordering
of some of the odd floating-point values discussed so far:
public class Fp6 {
static double[] list = {
Double.NEGATIVE_INFINITY,
-0.0,
+0.0,
Double.MIN_VALUE,
Double.MAX_VALUE,
Double.POSITIVE_INFINITY
};
public static void main(String[] args) {
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}
The result of running the program is: -Infinity
-0.0
0.0
4.9E-324
1.7976931348623157E308
Infinity
The list presents -0.0 before +0.0. As already mentioned,
whether -0.0 really comes before +0.0 depends on your
perspective.
NaN is not on the list because it is not
ordered with respect to other values.
Note that Double.MAX_VALUE and
Double.POSITIVE_INFINITY are distinct values.
This tip looked at a few of the properties of
floating-point arithmetic. It pays to be careful when using
floating-point because it behaves differently than you might
expect if you simply apply the laws of mathematics.
For more information about floating-point arithmetic, see
section 4.2.3, Floating-Point Types, Formats, and Values, in
"The
JavaTM Language Specification
Second Edition" by Gosling, Joy, Steele, and Bracha. Also
see the following IEEE-754 documents: Storage
Layout and Ranges of Floating-Point Numbers and IEEE-754
Floating-Point Conversion
IMPORTANT: Please read our Terms of Use, Privacy,
and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developer.java.sun.com/berkeley_license.html
Comments? Send your
feedback on the Core JavaTM Technologies Tech Tips
to: jdc-webmaster@sun.com
Subscribe to other Java
developer Tech Tips:
-
Enterprise Java Technologies Tech Tips. Get tips on using
enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EETM). -
Wireless Developer Tech Tips. Get tips on using wireless Java
technologies and APIs, such as those in the Java 2 Platform,
Micro Edition (J2METM).
To subscribe to these and other JDC
publications: - Go to the JDC Newsletters and Publications
page, choose the newsletters you want to subscribe to and click "Update". - To
unsubscribe, go to the subscriptions page, uncheck the
appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies
Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html
Copyright 2003 Sun Microsystems, Inc. All rights
reserved. 901 San Antonio Road, Palo Alto, California
94303 USA.
This document is
protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html
Sun, Sun Microsystems, Java,
Java Developer Connection, J2SE, J2EE, and J2ME are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.
|