|
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. PERFORMING EXACT CALCULATIONS WITH FLOATING-POINT NUMBERSSuppose 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:
The total value of these coins is $3.61. Here's a program that adds together the coin values:
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:
This program displays the raw bit patterns used internally to represent floating-point fractional values. The output is:
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 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
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:
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
and the output is:
sum = 3.61
This example provides values such as 0.1 to the
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 USING ENUMERATIONS IN JAVA PROGRAMMINGAn 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:
When you run the program, the output is:
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:
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
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 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 " 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 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:
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 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:
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 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 - 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: - 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. | |||||||||||||||||||||||||
|
| ||||||||||||