Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

Performing Exact Calculations With Floating-Point Numbers and Using Enumerations in Java Programming

 

Tech Tips Archive


WELCOME to the Java Developer Connection (JDC) Tech Tips, August 7, 2001. This issue covers:

These tips were developed using Java 2 SDK, Standard Edition, v 1.3.

Pixel

PERFORMING EXACT CALCULATIONS WITH FLOATING-POINT NUMBERS

Suppose that you're developing an application in the Java programming language, and you need to do some financial calculations. You have some coins, and you want to add the values of the coins together to find the total value. Let's say that you use United States dollars, and assume the following coins and quantities:

pennies (0.01) 1
nickels (0.05) 3
dimes (0.10) 7
quarters (0.25) 3
half dollars (0.50) 4

The total value of these coins is $3.61.

Here's a program that adds together the coin values:

public class FpcDemo1 {
    
    // record type for cents/count pairs
    static class Rec {
        double cents;
        int count;
        Rec(double cents, int count) {
            this.cents = cents;
            this.count = count;
        }
    }
    
    // set of records
    static Rec values[] = {
        new Rec(0.01, 1),
        new Rec(0.05, 3),
        new Rec(0.10, 7),
        new Rec(0.25, 3),
        new Rec(0.50, 4)
    };
    
    // compute relative error and take its 
    // absolute value
    static double getRelativeError(double obs, 
      double exp) {
        if (exp == 0.0) {
            throw new ArithmeticException();
        }
        return Math.abs((obs - exp) / exp);
    }
    
    public static void main(String args[]) {
        double sum = 0.0;
    
        // add up the values of the coins
    
        for (int i = 0; i < values.length; i++) {
            rec r = values[i];
            sum += r.cents * r.count;
        }
    
        // print the sum and the difference 
        // from 3.61
    
        system.out.println("sum = " + sum);
    
        system.out.println("sum - 3.61 = " + 
         (sum - 3.61));
    
        // check to see if equal to 3.61
    
        if (sum == 3.61) {
            system.out.println("exactly equal 
              to 3.61");
        }
    
        // compute the relative error
    
        double rerr = getrelativeerror(sum, 3.61);
        system.out.println("relative error = " + 
          rerr);
    
        // check to see if sum approximately equal 
        // to 3.61
    
        if (rerr <= 0.01) {
            system.out.println("approximately equal 
              to 3.61");
        }
    }
}

This program is straightforward and simple, but unfortunately, it doesn't work. The first two lines of output are:

    sum = 3.6100000000000003

    sum - 3.61 = 4.440892098500626E-16

The problem is that some decimal numbers, like 0.1, have no exact floating-point representation.

Before examining this example further, it's worth looking a little more closely at the idea that some numbers such as 0.1, have no exact equivalent in floating-point. Here's another program that shows this:

public class FpcDemo2 {
    public static void main(String args[]) {
    
        // compute the bits that represent
        // the values 0.1 and 0.09375
    
        long bits1 = Double.doubleToRawLongBits(0.1);
        long bits9375 = 
          Double.doubleToRawLongBits(0.09375);
    
        // extract and display bits 51-0 of each value
    
        long mask = 0xfffffffffffffL;
        String s1 = Long.toBinaryString(bits1 & mask);
        String s9375 = Long.toBinaryString(bits9375 & 
          mask);
        System.out.println(s1);
        System.out.println(s9375);
    
        // display the result of multiplying 0.1 by 
        // 56.0
    
        System.out.println(0.1 * 56.0);
    }
}

This program displays the raw bit patterns used internally to represent floating-point fractional values. The output is:

1001100110011001100110011001100110011001100110011010

1000000000000000000000000000000000000000000000000000

5.6000000000000005

The first line of output is the bit pattern for 0.1, and the second is the pattern for 0.09375. The first pattern shows a repeating sequence, and in fact the value 1/10 is the sum of an infinite series of powers of two:

1/10 = 1/16 + 1/32 + 1/256 + 1/512 + 1/4096 + 
  1/8192 + ...

By contrast, 0.09375 is 3/32, that is:

    3/32 = 1/16 + 1/32

In other words, 0.09375 is exactly representable, and 0.1 is not. You can observe the effects of this by looking at the third line of output above, which is the product of 56.0 and 0.1. The resulting value is slightly off from the expected value 5.6.

One way of solving the problem is illustrated in the FpcDemo1 example, which uses the relative error method. Relative error is defined as:

relative error = (observed - expected) / expected

For example, the expected value is 3.61, but the actual value is slightly different. So applying the formula, you take the difference of the values, and divide it by 3.61. Then take the absolute value. The result is a percentage that shows how far off the actual value is from the expected value. This technique is generally useful in any sort of floating-point calculation, because there is often a problem with obtaining exact values. Say, for example, that the computed sum of the coin values must be within 1% of 3.61, then the values are in fact approximately equal according to this rule. The last two lines of output from the FpcDemo1 example are:

    relative error = 1.2301640162051597E-16

    approximately equal to 3.61  

Computing the relative error and doing approximate comparisons is quite useful, but there are still some problems with the example. One of them is a display issue. If you are expecting a value such as 3.61 and you instead get 3.6100000000000003, this result would probably not be acceptable output because, for example, it could overflow the width of a field. And you might, in fact, require an exact answer rather than an approximation. Such a requirement would be common, for example, in calculations that involve cash transactions with a retail customer. So let's look at a couple of other solutions to this problem.

One solution is to use whole values, that is, compute the sum in cents. Here's what the program looks like:

public class FpcDemo3 {
    
    // record for cents/count pairs
    static class Rec {
        int cents;
        int count;
        Rec(int cents, int count) {
            this.cents = cents;
            this.count = count;
        }
    }
    
    // set of records
    static Rec values[] = {
        new Rec(1, 1),
        new Rec(5, 3),
        new Rec(10, 7),
        new Rec(25, 3),
        new Rec(50, 4)
    };
    
    public static void main(String args[]) {
        int sum = 0;
    
        // sum up the values of the records
    
        for (int i = 0; i < values.length; i++) {
            rec r = values[i];
            sum += r.cents * r.count;
        }
    
        // display the sum
    
        system.out.println("sum = " + sum);
    
        // display the whole/fraction parts of the sum
    
        system.out.println((sum / 100) + "." + 
          (sum % 100));
    }
}


The output is:

    sum = 361

    3.61

The last line of the program illustrates how you can pick apart the summed value to get dollars and cents. This approach entirely avoids the representation problem. It is possible to extend this idea to any currency unit you wish, for example, 1/1000 cents. But this technique doesn't work so well if you need to compute fractional units, for example, 5% of 5 cents, or 0.25 cent.

A final approach uses the BigDecimal class, a class that supports arbitrary-precision signed decimal numbers. Using this class, the demo looks like this:

import java.math.BigDecimal;
    
public class FpcDemo4 {
    
    // record type for cents/count pairs
    static class Rec {
        String cents;
        int count;
        Rec(String cents, int count) {
            this.cents = cents;
            this.count = count;
        }
    }
    
    // set of records
    static Rec values[] = {
        new Rec("0.01", 1),
        new Rec("0.05", 3),
        new Rec("0.10", 7),
        new Rec("0.25", 3),
        new Rec("0.50", 4)
    };
    
    public static void main(String args[]) {
        BigDecimal sum = new BigDecimal("0.00");
    
        // sum up the values using BigDecimal
    
        for (int i = 0; i < values.length; i++) {
            rec r = values[i];
            bigdecimal cents = new bigdecimal(r.cents);
            bigdecimal count = new bigdecimal(r.count);
            sum = sum.add(cents.multiply(count));
        }
    
        // display the sum
    
        system.out.println("sum = " + sum);
    }
}

and the output is:

    sum = 3.61

This example provides values such as 0.1 to the BigDecimal constructor as strings, not as double values (which is also supported). Doing it in this way gets around the problem of not being able to exactly represent values such as 0.1. In other words, the BigDecimal class has its own arbitrary-precision representation, distinct from the IEEE 754 representation used for floating-point values. If you pass the constructor a value like "0.1", as a string, then the exact value is preserved. But if you use the constructor that takes a double argument, the representation problem reoccurs.

BigDecimal gets around the problems outlined above, but does it at some cost. You should not assume that calculations done using BigDecimal will be as fast as standard floating-point calculations, which are typically performed in hardware. If you plan to use BigDecimal, you might want to look at performance issues.

It's important to understand the limitations of floating-point in representing common fractional values such as 0.1. You also need to understand what techniques are available to get around these limitations.

For further information about the BigDecimal class, see the class description.

Pixel

USING ENUMERATIONS IN JAVA PROGRAMMING

An enumeration is a small group of constant values (enumerators or enumeration constants) that are related to each other. For example, you might have a set of colors like red, green, and blue, and you want to use these as constants in your program to specify the colors of graphical objects. Let's look at a simple example of using an enumeration:

class EnumColor {
    // private constructor so class is not instantiable
    private EnumColor() {}
    
    public static final int RED = 1;
    public static final int GREEN = 2;
    public static final int BLUE = 3;
}
    
public class EnumDemo1 {
    // print color based on argument
    static void printColor(int color) {
        if (color == EnumColor.RED) {
            System.out.println("red");
        }
        else if (color == EnumColor.GREEN) {
            System.out.println("green");
        }
        else {
            System.out.println("blue");
        }
    }
    
    public static void main(String args[]) {
        printColor(EnumColor.GREEN);
    }
}

When you run the program, the output is:

    green

EnumColor is a class used as a packaging vehicle for a set of constants. It has a private constructor to prevent users from creating objects of the class or extending it. Enumerators are referred to by expressions such as "EnumColor.GREEN".

This approach to implementing enumerations is very simple, works pretty well, is efficient, and is widely used in Java programming. But there are some problems with doing it this way, some of which appear in the following example:

import java.util.*;
    
// enumeration for colors
class EnumColor {
    private EnumColor() {}
    
    public static final int RED = 1;
    public static final int GREEN = 2;
    public static final int BLUE = 3;
}
    
// enumeration for booleans
class EnumBoolean {
    private EnumBoolean() {}
    
    public static final int TRUE = 1;
    public static final int FALSE = 2;
}
    
public class EnumDemo2 {
    static void printColor(int color) {
        if (color == EnumColor.RED) {
            System.out.println("red");
        }
        else if (color == EnumColor.GREEN) {
            System.out.println("green");
        }
        else {
            System.out.println("blue");
        }
    }
    
    public static void main(String args[]) {
        // assign a bogus value to color
        // and then print the color
    
        int color = 59;
        printColor(color);
    
        // assign color a value from
        // a different enumeration
    
        color = EnumBoolean.FALSE;
        printColor(color);
    
        // try to add a color to a list
    
        List list = new ArrayList();
        //list.add(EnumColor.BLUE);
    }
}

When you run the program, the output is:

    blue
    green

This example highlights a set of problems. The first is that the program is allowed to assign the value 59 to a variable that's supposed to represent a color. 59 is not a legal value for any of the enumerators within EnumColor. Then, when printColor is called, it fails to diagnose the fact that an illegal enumerator value was passed.

The program then assigns a value from a different enumeration to the color variable. This error is not caught. Finally, when the program tries to add an enumerator to a list, the result is a compiler error (you need to uncomment the last line in EnumDemo2 to see this error)

These problems have a root cause: specifying a Java enumeration based on int values does not establish a distinct enumeration type. In other words, if an enumeration consists of a set of int constants, there is nothing that supports detection of illegal values that are not part of the enumeration type. There is no way to enforce type rules, for example, the usual rules that say you can't assign a reference of one class type to a reference of an unrelated type.

There are some further problems with using int values to represent enumerations. One is that there is no "toString" mechanism, that is, no easy way to associate "2" with "green". You have to write a method "printColor" for this.

Another problem is that the constant values are bound into client code that uses the values. You can see this by saying:

    javac EnumDemo2.java

    javap -c -classpath . EnumDemo2

and examining the printColor method. For example, the sequence:

    1 iconst_1
    2 if_icmpne 16

compares the passed-in printColor method argument with the constant value 1 (EnumColor.RED). This behavior can lead to problems if the enumerator value changes and a recompilation of all affected classes is not done.

If you do use an int-based approach to enumerations, one simple thing you can do to improve code quality is define a method within the enumeration class that checks whether a given enumerator is valid:

public static boolean isValidEnumerator(int e) {
        return e == RED || e == GREEN || e == BLUE;
}

Then call this method as appropriate, to validate enumerator values.

There's another approach to implementing enumerations that gets around many of these problems. This technique has the name "typesafe enum", and it looks like this:

import java.util.*;
    
class EnumColor {
    // enumerator name
    private final String enum_name;
    
    // private constructor, called only within this class
    private EnumColor(String name) {
        enum_name = name;
    }
    
    // return the enumerator name
    public String toString() {
        return enum_name;
    }
    
    // create three enumerators
    public static final EnumColor RED =
        new EnumColor("red");
    public static final EnumColor GREEN =
        new EnumColor("green");
    public static final EnumColor BLUE =
        new EnumColor("blue");
}
    
class EnumBoolean {
    private final String enum_name;
    
    private EnumBoolean(String name) {
        enum_name = name;
    }
    
    public String toString() {
        return enum_name;
    }
    
    public static final EnumBoolean TRUE =
        new EnumBoolean("true");
    public static final EnumBoolean FALSE =
        new EnumBoolean("false");
}
    
class EnumDemo3 {
    public static void main(String args[]) {
    
        // assign an enumerator and then print the 
        // value
    
        EnumColor color = EnumColor.GREEN;
        System.out.println(color);
    
        // try to assign an enumerator to an
        // enumeration variable of a different type
    
        //color = EnumBoolean.FALSE;
    
        // add an enumerator to a list
    
        List list = new ArrayList();
        list.add(EnumColor.BLUE);
    
        // check to see if a color is blue
    
        color = EnumColor.BLUE;
        if (color == EnumColor.BLUE) {
            System.out.println("color is blue");
        }
    }
}

When you run the program, the output is:

    green
    color is blue

The idea is that you have a class representing an enumeration type. Within the class a set of enumerators is defined as instances of the class, referenced by static final fields. The class specifies a private constructor, meaning that there is no way for users of the class to create class objects or to extend the class. So the set of static constant enumerator objects within the class are the only objects of the class that exist.

When each static object is created, representing an enumerator, a string is passed to the constructor, specifying the name of the enumerator. So the toString problem mentioned earlier is solved. And because each class representing an enumeration is of a distinct type, the compiler automatically catches problems such as assigning an enumerator to a reference of an unrelated enumeration type.

The problem with integer constants bound into compiled code is solved. That's because the compiler refers to the static object fields of the enumeration class rather than compiling integer constants into the client code. And you can add enumeration constants to collections like ArrayList, because they are objects rather than primitive values like int.

If you use a typesafe enum, you need to check whether an object reference of such a type is null before checking for specific enumerator values:

void f(EnumColor e) {
    if (e == null) {
        throw new NullPointerException();
    }
}

After this check, you are guaranteed to have a valid enumeration value, one of the set of constants established within the enumeration class.

What about performance? Since enumerators are unique, you can use the operator == to check for reference identity. This is very fast. There is no need to use equals() to check for equality of enumeration constants.

There are some features you give up with typesafe enums. Unlike int-based enumerations, you can't use object-based enumeration constants as array indices, switch constants, or as bit masks to access a bit within a set of bits.

Typesafe enums solve a set of serious programming problems, and are worth using in your programs as a way of improving code quality and maintainability.

For more information, see item 21 "Replace enum constructs with classes" in "Effective Java Programming Language Guide" by Joshua Bloch; and Section 13.4.8 "final Fields and Constants" in " The Java Language Specification Second Edition" by Gosling, Joy, Steele, and Bracha

Pixel

- NOTE

Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page, uncheck the appropriate checkbox, and click the Update button.

As of May 22, 2001, Sun Microsystems updated its Privacy Policy to give you a better understanding of Sun's Privacy Policy and Practice. If you have any questions, contact privacy@sun.com.

- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the Subscriptions page, choose the newsletters you want to subscribe to, and click Update.

- FEEDBACK

Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com

- ARCHIVES

You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html

- COPYRIGHT

Copyright 2001 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

- LINKS TO NON-SUN SITES

The JDC Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource.

This issue of the JDC Tech Tips is written by Glen McCluskey.

JDC Tech Tips August 7, 2001

Sun, Sun Microsystems, Java, and Java Developer Connection are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.