Subject: Core Java Technologies Tech Tips, Feb. 4, 2003 (Floating-Point Arithmetic)
image
image  
Core Java Technologies Technical Tips
image
   View this issue as simple text February 4, 2003    

In this Issue

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.

Pixel
Pixel
Pixel

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

Pixel
Pixel
Pixel

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about JavaTM programming? Use Java Online Support.

Pixel
Pixel

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.

Sun Microsystems, Inc.
Please unsubscribe me from this newsletter.