Return-Path: <env_-8403107424430966804@hermes.java.sun.com>
Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id SAA02506; Tue, 7 Aug 2001 18:02:23 -0400 (EDT)
Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85])
	by fort-point-station.mit.edu (8.9.2/8.9.2) with SMTP id SAA16456
	for <alexp@mit.edu>; Tue, 7 Aug 2001 18:02:23 -0400 (EDT)
Message-Id: <200108072202.SAA16456@fort-point-station.mit.edu>
Date: Tue, 7 Aug 2001 15:02:23 PDT
From: "JDC Tech Tips" <body_-8403107424430966804@hermes.java.sun.com>
To: alexp@mit.edu
Subject: JDC Tech Tips  August 7, 2001
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


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

         * Performing Exact Calculations With Floating-Point 
           Numbers
         * Using Enumerations in Java Programming
                  
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0807.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
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 at
http://java.sun.com/j2se/1.3.0/docs/api/java/math/BigDecimal.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 (http://java.sun.com/docs/books/effective/); and 
Section 13.4.8 "final Fields and Constants" in "The Java Language 
Specification Second Edition" by Gosling, Joy, Steele, and Bracha
(http://java.sun.com/docs/books/jls/)

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

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


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page 
http://developer.java.sun.com/subscription/
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.


	To use our one-click unsubscribe facility, select the following URL:
	http://hermes.java.sun.com/unsubscribe?-8403107424430966804
