Note: In all of the examples I use standard US notation of commas representing a numeric group separator and period as a decimal point. The number 1,000.00 is the number one thousand.
import java.text.*; public class CantAdd { public static void main(String[] args) { float a = 8250325.12f; float b = 4321456.31f; float c = a + b; System.out.println(NumberFormat.getCurrencyInstance().format(c)); } } |
This prints out $12,571,782.00, which is incorrect. The correct answer is $12,571,781.43. So what's the problem? Doesn't Java know how to add two numbers together? Actually, the problem is much older than Java and can be found in the IEEE 754 specification that defines how floating-point numbers work. As at least one software engineer has commented, "floating-point is designed to give you the wrong answer very quickly." This is not far from the truth.
Floating-point numbers are actually made up of three different parts stored in a 32-bit field. The first bit is a sign bit. The next eight bits are the exponent and the remaining twenty-three bits are the significand. The exponent holds the highest power of 2 that is smaller than the number. The remainder is stored in the significand. Let's look at the number 1,000. The highest power of 2 less than 1,000 is 2 to the power of 9, which is 512. We add 127 to 9 (that is so we can store negative exponents without using a sign bit) and store that in the exponent. The remainder of 488 is stored in the significand. The significand is divided into two portions. In our example, the first 9 bits (the size of the exponent) represents the integral portion of the significand and the remaining 14 bits represents the decimal portion. Here is what the bit positions look like (I inserted a decimal point in the significand to show the two seperate sections):
0 10001000 111101000.00000000000000 |
The first part is the sign so we know we are dealing with a positive number. The second portion is the exponent. The bit pattern represents 136. If we subtract 127 we get 9, which is the exponent we expect. Since the exponent is 9, the first 9 bits of the significand represents the integral portion of our number. Converting that to decimal gives us 488. The remainder of the significand represents the decimal portion, which is zero.
Now lets look at our problem number, 12,571,781.43. Converting that to float and displaying the bits gives us:
0 10010110 01111111101010010000101 |
The sign is positive. The exponent is 150 which after subtracting 127 leaves us with 23. When we calculate 2 to the 23rd power we get 8,388,608. Subtract that from 12,571,781.43 and we are left with 4,183,173.43 that must be stored in the significand. The first 23 bits (because our exponent is 23) represents the integral portion. But wait! If we use 23 bits for the integral portion then we are left with nothing for the decimal portion. This means that the number 12,571,781.43 can only be stored as 12,571,781 in a float. Follow this example for the two values we are attempting to add in our test program above and you will see how we end up with the result we get.
What if the exponent is greater than 23? In that case, the decimal number is calculated and then zeroes are appended to end for the missing digits. In other words we are losing precision in order to hold a larger number.
The conclusion is that an int variable (31 significant bits) can actually store a larger whole number more accurately than a float variable (23 significant bits). A float can store a bigger number than an int because of the exponent but it can only hold about eight significant digits. In our test program above, we had nine significant digits in the two numbers we were adding and ten significant digits in the result so we ended up with the wrong answer.
We now know that unless we need to work with less than one million dollars that float is not going to work for us. So what do we do for large monetary calculations? There are at least two solutions that are commonly used. The first is to do everything using integral types. You will need to remember that "590" is really "$5.90" or your payroll system will be giving everyone big raises. You may, however, run into problems if you need to do percent calculations rounding to the nearest penny since ints don't support this type of calculation. You may want to write a class or two to help you out with this.
There is, however, a better solution. Douglas Dunn ("Java Rules", p.235) has suggested that Java may be the first programming language where using a class instead of primitives to do monetary calculations may become the norm for mainstream business applications. That class is what the rest of this article will discuss.
Here is the BigDecimal version of our test program:
import java.text.*; import java.math.*; public class CanAdd { public static void main(String[] args) { BigDecimal a1 = new BigDecimal("8250325.12"); BigDecimal b1 = new BigDecimal("4321456.31"); BigDecimal c1 = a1.add(b1); System.out.println(NumberFormat.getCurrencyInstance().format(c1)); } } |
This program prints out the correct answer, $12,571,781.43. In fact, the BigDecimal class can handle any number no matter how large it is (within the limitations of your computer's memory). How does it do it? BigDecimal stores numbers in an array. Each entry in the array represents a digit. The larger the number required, the larger the array that BigDecimal creates to hold the number. The scale (number of digits to the right of the decimal) is stored in an int.
Running our test program with these statements will still produce the correct answer:
BigDecimal a1 = new BigDecimal("8273872368712658763457862348566489.7162578164578825032512"); BigDecimal b1 = new BigDecimal("8762347526136571645982560956723521.8374618726457432145631"); BigDecimal c1 = a1.add(b1); System.out.println(c1); |
You will discover one problem in attempting to print these very large numbers using the NumberFormat class. The format method of the NumberFormat class converts BigDecimal to a double before formatting which limits us to only 16 significant digits. It would have been nice if the designers of the NumberFormat class had added a method to specifically handle BigDecimal.
BigDecimal has four constructors but the one you will want to use most is the one that takes a String. If we ran our test program using the constructor that takes a double, we would still get the wrong answer if we used float constants:
BigDecimal a1 = new BigDecimal(8250325.12f); BigDecimal b1 = new BigDecimal(4321456.31f); |
The reason this doesn't work correctly is that the float constants will undergo loss of precision before they are sent to the constructor of the BigDecimal. Using the String constructor will always allow the BigDecimal to represent exactly the number you want.
BigDecimal supports add, subtract, multiply, and divide. The calculated scale for add and subtract is simply the larger scale of the two numbers you are working with. The calculated scale for multiply is the sum of the scale of the two numbers. The scale of the divide method is either the scale you specify in the method or the scale of the current BigDecimal object depending on which overloaded version of the BigDecimal you are using. The divide also requires that you specify a rounding method. ROUND_HALF_UP is the standard arithmetic rounding method used most often. Lets take a look at this code fragment:
BigDecimal a1 = new BigDecimal("2"); BigDecimal b1 = new BigDecimal("3"); BigDecimal c1 = a1.divide(b1, 9, BigDecimal.ROUND_HALF_UP); System.out.println(c1); |
This will print: 0.666666667. Since we specified a scale of 9, we get 9 digits after the decimal point. Since we specified "BigDecimal.ROUND_HALF_UP," we get the last digit mathematically rounded.
BigDecimal supports the use of the compareTo method for comparing values. In most cases you will want to use the compareTo even for equals comparisons because the equals method considers two numbers with the exact same value but different scales to be not equal. In other words, the equals method considers 2, 2.0, 2.00, and 2.000 to be different numbers. The compareTo will consider all of them to be equal.
I hope this has removed some of the mystery of the float primitives and has given you the desire to look into the BigDecimal class as a way to handle decimal arithmetic. The BigDecimal is the most accurate way to do arbitrary precision calculations. In most business applications, it is well worth the cost of the overhead of working with objects for the added accuracy you get with the BigDecimal class.
A lot of the information for this article was derived from the book, "Java Rules" by Douglas Dunn. This is a great book that should be owned by everyone who wants to understand the workings of the Java language.
Ilja Preuss pointed out an article dealing with formatting BigDecimal numbers. As I mentioned above, the NumberFormat class doesn't do a good job with BigDecimal. The article from Tech-Tips provides at least one solution.
Jason Menard wrote a nine horseshoe review of a book entitled, "Java Number Cruncher" by Ronald Mak. The book deals with number issues in Java including the problem of dealing with the IEEE 754 specification.