Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do two floating-point results differ when they nominally have the same value?

I was recently reading about storing floating point values in the memory. And I've written a small program to test what I've read. And I noticed that there is a difference in the way Java processes the floating point values.

public class Test
{
   public static void main(String args[])
   {
     double a = 0.90;
     System.out.println(a);
     System.out.println(2.00-1.10);
   }
 }

The above program is printing

0.9
0.8999999999999999

Why both these statements are not printing the same value? I know some floating values can't be represented exactly. In that case, both should give same value.

like image 368
Santhosh Reddy Mandadi Avatar asked Oct 31 '12 14:10

Santhosh Reddy Mandadi


2 Answers

Why both these statements are not printing the same value?

The result is not the same.

I know some floating values can't be represented exactly.

So you should assume that the result of an operation can depend on the amount of representation error of the values you use.

for (long l = 1; l <= 1e16; l *= 10) {
    double a = l + 2;
    double b = l + 1.1;
    System.out.println(a + " - " + b + " is " + (a - b));
}

as the value gets larger the representation error increases and gets larger compares with the result of 0.9

3.0 - 2.1 is 0.8999999999999999
12.0 - 11.1 is 0.9000000000000004
102.0 - 101.1 is 0.9000000000000057
1002.0 - 1001.1 is 0.8999999999999773
10002.0 - 10001.1 is 0.8999999999996362
100002.0 - 100001.1 is 0.8999999999941792
1000002.0 - 1000001.1 is 0.9000000000232831
1.0000002E7 - 1.00000011E7 is 0.900000000372529
1.00000002E8 - 1.000000011E8 is 0.9000000059604645
1.000000002E9 - 1.0000000011E9 is 0.8999999761581421
1.0000000002E10 - 1.00000000011E10 is 0.8999996185302734
1.00000000002E11 - 1.000000000011E11 is 0.899993896484375
1.000000000002E12 - 1.0000000000011E12 is 0.9000244140625
1.0000000000002E13 - 1.00000000000011E13 is 0.900390625
1.00000000000002E14 - 1.000000000000011E14 is 0.90625
1.000000000000002E15 - 1.0000000000000011E15 is 0.875
1.0000000000000002E16 - 1.0000000000000002E16 is 0.0

and on the topic of when representation error gets so large your operation does nothing.

for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
    if (d == d + 1) {
        System.out.println(d + " + 1 == " + (d + 1));
        break;
    }
}
for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
    if (d == d - 1) {
        System.out.println(d + " - 1 == " + (d - 1));
        break;
    }
}

prints

9.007199254740992E15 + 1 == 9.007199254740992E15
1.8014398509481984E16 - 1 == 1.8014398509481984E16
like image 143
Peter Lawrey Avatar answered Oct 05 '22 22:10

Peter Lawrey


When “0.90” is converted to double, the result is .9 plus some small error, e0. Thus a equals .9+e0.

When “1.10” is converted to double, the result is 1.1 plus some small error, e1, so the result is 1.1+e1.

These two errors, e0 and e1, are generally unrelated to each other. Simply put, different decimal numbers are different distances away from binary floating-point numbers. When you evaluate 2.00-1.10, the result is 2–(1.1+e1) = .9–e1. So one of your numbers is .9+e0, and the other is .9-e1, and there is no reason to expect them to be the same.

(As it happens in this case, e0 is .00000000000000002220446049250313080847263336181640625, and e1 is .000000000000000088817841970012523233890533447265625. Also, subtracting 1.1 from 2 introduces no new error, after the conversion of “1.1” to double, by Sterbenz’ Lemma.)

Additional details:

In binary, .9 is .11100110011001100110011001100110011001100110011001100 11001100… The bits in bold fit into a double. The trailing bits do not fit, so the number is rounded at that point. That causes a difference between the exact value of .9 and the value of “.9” represented as a double. In binary, 1.1 is 1.00011001100110011001100110011001100110011001 10011001… Again, the number is rounded. But observe the amount rounding is different. For .9, 1100 1100… was rounded up to 1 0000 0000…, which adds 00110011… at that position. For 1.1, 1001 1001 is rounded up to 1 0000 0000…, which adds 01100110… at that position (and causes a carry in the bold bits). And the two positions are different; 1.1 starts to the left of the radix point, so it looks like this: 1.[52 bits here][place where rounding occurs]. .9 starts to the right of the radix point, so it looks like this: .[53 bits here][place where rounding occurs]. So the rounding for 1.1, besides being 01100110… instead of 00110011…, is also doubled because it occurs one bit to the left of the .9 rounding. So you have two effects making e0 different from e1: The trailing bits that were rounded are different, and the place where rounding occurs is different.

like image 35
Eric Postpischil Avatar answered Oct 05 '22 22:10

Eric Postpischil