3.7 Another Look at Roundoff Errors
In Chapter 1, we saw that the
float
value of
printed as
0.33333334.
What happens if we assign the
float
value to a
double
variable and then print the
double
variable's value? Will we get
0.3333333400000000
? Actually, what we get is
0.3333333432674408.
Where did the last eight digits come from? Are they random garbage? Program 3-4 attempts to find some answers. See Listing 3-4.
Listing 3-4 Roundoff errors of the number
.
package numbercruncher.program3_4;
import numbercruncher.mathutils.IEEE754;
/**
* PROGRAM 3-4: One Third
*
* Investigate the floating-point representation of 1/3.
*/
public class OneThird
{
public static void main (String args[])
{
float fThird = 1/3f;
double dConverted = fThird;
double dThird = 1/3d;
System.out.println(" Float 1/3 = " + fThird);
System.out.println("Converted to double = " + dConverted);
System.out.println(" Double 1/3 = " + dThird);
IEEE754 ieeeFThird = new IEEE754(fThird);
IEEE754 ieeeDConverted = new IEEE754(dConverted);
IEEE754 ieeeDThird = new IEEE754(dThird);
ieeeFThird.print();
ieeeDConverted.print();
ieeeDThird.print();
// Prepend the leading 0 bits of the converted 1/3.
int unbiased = ieeeDConverted.unbiasedExponent();
String bits = "1" + ieeeDConverted.fractionBits();
while (++unbiased < 0) bits = "0" + bits;
// Sum the indicated negative powers of 2.
double sum = 0;
double power = 0.5;
for (int i = 0; i < bits.length(); ++i) {
if (bits.charAt(i) == '1') sum += power;
power /= 2;
}
System.out.println();
System.out.println("Converted 1/3 by summation = " + sum);
}
}
Output:
Float 1/3 = 0.33333334
Converted to double = 0.3333333432674408
Double 1/3 = 0.3333333333333333
------------------------------
float value = 0.33333334
sign=0, exponent=01111101 (biased=125, normalized, unbiased=-2)
significand=1.01010101010101010101011
------------------------------
double value = 0.3333333432674408
sign=0, exponent=01111111101 (biased=1021, normalized, unbiased=-2)
significand=1.0101010101010101010101100000000000000000000000000000
------------------------------
double value = 0.3333333333333333
sign=0, exponent=01111111101 (biased=1021, normalized, unbiased=-2)
significand=1.0101010101010101010101010101010101010101010101010101
Converted 1/3 by summation = 0.3333333432674408
Let's examine
. Its unbiased exponent value is -2, so we need to shift the
implied
point of its significand two places to the left, giving us the value 0.0101010101010101010101011 in base 2. We can verify it by doing base 2 division:
The IEEE 754 representation of
rounded up the last bit from 0 to 1, thus introducing a very small positive error. We see this error as the final digit 4 (instead of 3) when we print out the
float
value.
Program 3-4's output also shows what really happens when we convert the
float
value to a
double.
This widening operation is exact: It appends 29 (= 53 - 24) zero bits at the right. But when we converted that
double
value to a decimal number for printing, we didn't get eight decimal zeros at the end; instead, we got what appear to be the garbage digits. For comparison, Program 3-4 also computes and displays the
double
value of
.
In fact, though, that "garbage" is quite valid, as the latter part of the program
demonstrates
. Using the binary representation of
, we add the indicated negative powers of 2. The printed sum matches what was printed for the converted value.
As we saw in Chapter 1, a roundoff error occurs when an exact value, such as
, lies between two representable floating-point values. How does Java decide which of the two floating-point values to choose? In the case of the
float
representation of
, how did Java decide the last bit should be 1 instead of 0?
When an exact values lies between two representable floating-point values, Java picks the floating-point value that is
closest
to the exact value. If the exact value lies exactly halfway between two floating-point values, Java picks the floating-point value whose least significant (rightmost) bit is 0. This corresponds to the default rounding mode called
round to
nearest
in the IEEE 754 standard.
The IEEE 754 standard defines several other rounding modes for floating-point:
round down, round up,
and
round toward zero.
Once again, Java deviates from the standard.
Java does not implement the nondefault rounding modes defined by the standard.
If you want your Java program to use these other rounding modes, you can write
methods
that emulate the floating-point operations with the desired modes, or your program can invoke floating-point routines written in other languages.