Want more information ?. Try the Web Home Page for this document.

PSPDec versus Java floating point

Java offers two built-in data types float and double for numbers with fractional portions. These have equivalent Java classes java.lang.Float and java.lang.Double. The standard Java follows for these floating-point numbers is IEEE 754.

If you use Java floating-point data types and classes for decimal numbers you must be absolutely sure that you never encounter numeric values beyond which floating-point is inaccurate. If you get this wrong, numeric values are silently incorrect. You also need to carefully consider issues of scale and rounding for arithmetic operations - particularly multiplication and division. You must also be prepared to deal with input/output values in exponential format.

With true decimal classes like PSPDec, accuracy is not negotiable. You specify up front exactly how many digits you want either side of the decimal place, and the numeric value is 100% accurate within those limits. You have complete control over rounding and truncation. We believe that users of your Java applications will expect nothing less.

Why accept any compromise ?. Why live with 'good enough' ?. If you want your business applications to be taken seriously, we honestly believe you should not use Java floating-point for decimal numbers.

The IEEE 754 floating-point standard

This standard specifies the internal representation of float values stored in 32 bits (4 bytes) and double values stored in 64 bits (8 bytes).

The IEEE 754 standard and other floating-point standards like it are complicated. If you're interested, you can get more details about these standards on the Internet. Try the IEEE at www.ieee.org or the equally useful ACM at www.acm.org. Unfortunately you will not get far without hitting security/membership issues on both these sites. A better approach is to do a Web search - using Excite or similar - for 'IEEE 754 accuracy'. Whatever way you approach it, you will soon get into fairly esoteric documents - the densest of which use Adobe's PDF (Acrobat) format.

Apart from the theory of IEEE 754, what are the real-world issues you face when Java floating-point data types or classes ?.

Java floating point storage

Given a fixed limit on the number of bits (32 or 64) that can be used to store floating-point numeric values in Java, it is obvious that there will always be some numeric value that can not be stored completely accurately. The accuracy depends partly upon the value of the number e.g. 10, 1000000 and 100000000000000000000 can all be stored perfectly because the values are simple when expressed in exponential format but 11, 1000001 and 100000000000000000001 are not as easy.

IEEE 754 guarantees only a certain total number of digits will be stored accurately whatever their value. This is 6 digits for float, and 15 digits for double.

So float is safe only for relatively small numbers (like percentages), while doubles could cover everything else (exchange rates, account balances, transaction amounts etc.). It is simply not safe to use float/double beyond their 6/15 digit limitations. You may get accuracy for certain values (generally up to 9 digits for float and up to 17 digits for double), but not for all values.

Java floating point storage - example

Compile the following Java class T1:

import java.math.*;
class T1 {
    public static void main(String[] args) {
    if (args.length > 0) {
      Float F1 = new Float(args[0]);
      float f1 = F1.floatValue();
      Double D1 = new Double(args[0]);
      double d1 = D1.doubleValue();
      System.out.println("The input string " + args[0] + " converted to float is  " + f1);
      System.out.println("The input string " + args[0] + " converted to double is " + d1);
    }
  }
}

Then run the following from the command line:

C:\>java T1 .12345678
The input string .12345678 converted to float is  0.12345678
The input string .12345678 converted to double is 0.12345678
C:\>java T1 .123456789
The input string .123456789 converted to float is  0.12345679
The input string .123456789 converted to double is 0.123456789
C:\>java T1 12.3456789
The input string 12.3456789 converted to float is  12.345679
The input string 12.3456789 converted to double is 12.3456789
C:\>java T1 .100200091
The input string .100200091 converted to float is  0.100200094
The input string .100200091 converted to double is 0.100200091
C:\>java T1 .1234567890123456
The input string .1234567890123456 converted to float is  0.12345679
The input string .1234567890123456 converted to double is 0.1234567890123456
C:\>java T1 .12345678901234567
The input string .12345678901234567 converted to float is  0.12345679
The input string .12345678901234567 converted to double is 0.12345678901234566

The above illustrates the accuracy with which Java stores float and double values. Note that internal conversions are done silently (without warning messages) and that the actual number of digits stored accurately depends upon the value. The values above are accurate for 8/16 digits, but other values are still only guaranteed for 6/15 digits.

Java floating point calculations

For arithmetic operations, standards such as IEEE 754 specify exactly what should be done to preserve accuracy in calculated results. This involves the use of internal storage invisible to your Java program. The main point here is that 'accuracy' to the designers of IEEE 754 is not a simple concept.

Basically, the limitations of floating-point internal storage apply equally to transient values such as those returned within calculations. So you need to consider 6/15 digit limitations for the number of float/double digits in both factors in numerical calculations. This is more important for multiply/divide than for add/subtract. For example, if you have a percentage field with 4 digits (up to .9999 representing 99.99%) which you use in a double multiplication, you should plan on the other factor having no more than 11 digits in total.

Where intermediate calculations (particularly multiply/divide) are used, it is clear that double is safer than float for all but the smallest numerical values.

Java floating point rounding

In all the discussions above there has been no mention of scale (the number of digits after the decimal place). This is simply because floating-point sets no limit on scale particularly, just on the numeric accuracy of all digits.

So if you multiply a percentage figure (say .9999) by a US$ figure (say 999999.99), you will get a floating-point number that has up to 6 digits after the decimal place. If this result is also a US$ figure, you need to eliminate the 3rd-6th trailing scale digits. This is a rounding/truncation issue which crops up repeatedly as a 'newbie' question in the Java newsgroups.

One option is to use the Java NumberFormat class:

import java.text.NumberFormat;
class T2 {
    public static void main(String[] args) {
    if (args.length > 0) {
      NumberFormat nf = NumberFormat.getNumberInstance() ;
      nf.setGroupingUsed(false) ;     // don't group by threes
      nf.setMaximumFractionDigits(2) ;
      nf.setMinimumFractionDigits(2) ;
      Double D1 = new Double(args[0]);
      double d1 = D1.doubleValue();
      System.out.println("Double unformatted " + d1 + " now formatted " + nf.format(d1));
    }
  }
}

Compile the above and then run the following from the command line:

C:\>java T2 1234567.2349
Double unformatted 1234567.2349 now formatted 1234567.23
C:\>java T2 1234567.2350
Double unformatted 1234567.235 now formatted 1234567.24
C:\>java T2 123123123123
Double unformatted 1.23123123123E11 now formatted 123123123123.00
 

As you can see above, a double value can be restricted to a fixed number of decimal places (2 in the code above) with rounding. Of course the value of the underlying double itself is unaltered, but you can reassign the formatted string value back to the double itself.

Other solutions are possible. All involve additional coding because the Float/Double Java classes themselves do not allow you to impose restrictions on scale.