2

Here's an oddity (to me, at least). This routine prints true:

double x = 11.0;
double y = 10.0;
if (x-y == 1.0) {
    // print true
} else {
    // print false
}

But this routine prints false:

double x = 1.1;
double y = 1.0;
if (x-y == 0.1) {
    // print true
} else {
    // print false
}

Anyone care to explain what's going on here? I'm guessing it has something to do with integer arithmetic for ints posing as floats. Also, are there other bases (other than 10) that have this property?

Marius Burz
  • 4,555
  • 2
  • 18
  • 28
PengOne
  • 48,188
  • 17
  • 130
  • 149
  • 7
    Isn't this the usual double/floating point error? You simply can not compare doubles using ==. See http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems – theomega Sep 02 '11 at 21:27
  • @theomega: thanks for the link, but this still doesn't explain why the first case works. – PengOne Sep 02 '11 at 21:28
  • 2
    It does. You can represent 1.0 in binary, but you can't represent 0.1. Well ok you can but not with double. That's why (11.0-10.0) == 1.0 but (1.1-1.0) != 0.1 when you're using double. You can take a look at BigDecimal if you need such features. – Mateusz Dymczyk Sep 02 '11 at 21:30
  • possible duplicate of [Java floating point arithmetic](http://stackoverflow.com/questions/1661273/java-floating-point-arithmetic) – Paŭlo Ebermann Sep 02 '11 at 21:33
  • 1
    This document should clarify many questions you might have about floating point: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html) – fvu Sep 02 '11 at 21:35

3 Answers3

9

1.0 has an exact binary representation. 0.1 does not.

perhaps you are asking why 0.1 is not stored as a mantissa of 1 and an exponent of -10? but that's not how it works. it's not a decimal number plus an exponent, but a binary number. so "times 10" is not a natural thing.

sorry, maybe the last part is unclear. it's better to think of the exponent as a shift of bits. no shift of bits will convert an infinite sequence like 0.1 (decimal) into a finite one.

PengOne
  • 48,188
  • 17
  • 130
  • 149
andrew cooke
  • 45,717
  • 10
  • 93
  • 143
  • @PengOne: Yes, `0.5` is exactly representable, as well as `0.25` and `0.75`, or `0.125`. – Paŭlo Ebermann Sep 02 '11 at 21:35
  • @paulo: yep, i just checked those as well. thanks for the confirmation. – PengOne Sep 02 '11 at 21:36
  • 1
    i'm afraid you're mistaken. just because it sometimes works out doesn't mean it's ok. computers work in binary. that means that everything is based around the number 2. so 0.1 and 0.2 are going to have a very closely related representation (because one is a multiple of 2 of the other). so it's likely that subtracting one from the other gives a "round" result. but that doesn't mean that either has a finite representation in binary. – andrew cooke Sep 02 '11 at 22:38
  • @Andrew Learning and then getting rewarded... might become addicted to this kind of treatment... Cheers! – Marius Burz Sep 03 '11 at 00:41
4

Edit
I stand corrected by Andrew. Thank you!

Java follows IEEE 754 with a Base of 2, so it cannot represent 0.1 correctly (it is aprox. 0.1000000000000000055511151231257827021181583404541015625 or 1.1001100110011001100110011001100110011001100110011010 * 2^-4 in IEEE) which you can find out based on the binary representation of the double like this (bit 63 = sign, bits 62-52 = exponent and bits 51-0 being the mantissa):

long l = Double.doubleToLongBits(0.1);
System.out.println(Long.toBinaryString(l));

I just got carried away by the results and I thought for a moment that the floats in Java are working with a Base of 10 in which case it would have been possible to represent 0.1 just fine.

And now to hopefully clear the things once and for all, here's what goes on:

BigDecimal bigDecimal1 = new BigDecimal(0.1d);
BigDecimal bigDecimal2 = new BigDecimal(1.1d - 1.0);
BigDecimal bigDecimal3 = new BigDecimal(1.1d);
BigDecimal bigDecimal4 = new BigDecimal(1.0d);
System.out.println(bigDecimal1.doubleValue());
System.out.println(bigDecimal2.doubleValue());
System.out.println(bigDecimal3.doubleValue());
System.out.println(bigDecimal4.doubleValue());
System.out.println(bigDecimal1);
System.out.println(bigDecimal2);
System.out.println(bigDecimal3);
System.out.println(bigDecimal4);

Outputs:

0.1
0.10000000000000009
1.1
1.0
0.1000000000000000055511151231257827021181583404541015625
0.100000000000000088817841970012523233890533447265625
1.100000000000000088817841970012523233890533447265625
1

So what happens? 1.1 - 1.0 is equivalent to:
1.100000000000000088817841970012523233890533447265625 - 1 (Java can't represent 1.1 precisely) which is 0.100000000000000088817841970012523233890533447265625 and this is different than the way Java represent 0.1 internally (0.1000000000000000055511151231257827021181583404541015625)

If you're wondering why the result of the subtraction is being displayed as 0.10000000000000009 and the "0.1" is displayed as it is, have a look over here

Marius Burz
  • 4,555
  • 2
  • 18
  • 28
  • 1
    Thanks, but I was looking for **why**, which I think @andrew answered. – PengOne Sep 02 '11 at 21:33
  • this answer http://stackoverflow.com/questions/1661273/java-floating-point-arithmetic/1661624#1661624 in the thread you link to explains why you are mistaken here. – andrew cooke Sep 02 '11 at 22:40
0

This comes up in currency calculations all the time. Use BigDecimal if you need exact numerical representation at the cost of not having hardware enabled performance, of course.

Julien Chastang
  • 17,592
  • 12
  • 63
  • 89