hedleyyan
hedleyyan

Reputation: 467

Why `2.0 - 1.1` and `2.0F - 1.1F` produce different results?

I am working on a code where I am comparing Double and float values:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 1.1);            // 0.8999999999999999
        System.out.println(2.0 - 1.1 == 0.9);     // false

        System.out.println(2.0F - 1.1F);          // 0.9
        System.out.println(2.0F - 1.1F == 0.9F);  // true
        System.out.println(2.0F - 1.1F == 0.9);   // false
    }
}

Output is given below:

0.8999999999999999
false
0.9
true
false

I believe the Double value can save more precision than the float.

Please explain this, looks like the float value is not lose precision but the double one lose?


Edit: @goodvibration I'm aware of that 0.9 can not be exactly saved in any computer language, i'm just confused how java works with this in detail, why 2.0F - 1.1F == 0.9F, but 2.0 - 1.1 != 0.9, another interesting found may help:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 0.9);            // 1.1
        System.out.println(2.0 - 0.9 == 1.1);     // true

        System.out.println(2.0F - 0.9F);          // 1.1
        System.out.println(2.0F - 0.9F == 1.1F);  // true
        System.out.println(2.0F - 0.9F == 1.1);   // false
    }
}

I know I can't count on the float or double precision, just.. can't figure it out drive me crazy, whats the real deal behind this? Why 2.0 - 0.9 == 1.1 but 2.0 - 1.1 != 0.9 ??

Upvotes: 2

Views: 1076

Answers (2)

goodvibration
goodvibration

Reputation: 6206

The difference between float and double:

Let's run your numbers in a simple C program, in order to get their binary representations:

#include <stdio.h>

typedef union {
    float val;
    struct {
        unsigned int fraction : 23;
        unsigned int exponent :  8;
        unsigned int sign     :  1;
    } bits;
} F;

typedef union {
    double val;
    struct {
        unsigned long long fraction : 52;
        unsigned long long exponent : 11;
        unsigned long long sign     :  1;
    } bits;
} D;

int main() {
    F f = {(float )(2.0 - 1.1)};
    D d = {(double)(2.0 - 1.1)};
    printf("%d %d %d\n"      , f.bits.sign, f.bits.exponent, f.bits.fraction);
    printf("%lld %lld %lld\n", d.bits.sign, d.bits.exponent, d.bits.fraction);
    return 0;
}

The printout of this code is:

0 126 6710886
0 1022 3602879701896396

Based on the two format specifications above, let's convert these numbers to rational values.

In order to achieve high accuracy, let's do this in a simple Python program:

from decimal import Decimal
from decimal import getcontext

getcontext().prec = 100

TWO = Decimal(2)

def convert(sign, exponent, fraction, e_len, f_len):
    return (-1) ** sign * TWO ** (exponent - 2 ** (e_len - 1) + 1) * (1 + fraction / TWO ** f_len)

def toFloat(sign, exponent, fraction):
    return convert(sign, exponent, fraction, 8, 23)

def toDouble(sign, exponent, fraction):
    return convert(sign, exponent, fraction, 11, 52)

f = toFloat(0, 126, 6710886)
d = toDouble(0, 1022, 3602879701896396)

print('{:.40f}'.format(f))
print('{:.40f}'.format(d))

The printout of this code is:

0.8999999761581420898437500000000000000000
0.8999999999999999111821580299874767661094

If we print these two values while specifying between 8 and 15 decimal digits, then we shall experience the same thing that you have observed (the double value printed as 0.9, while the float value printed as close to 0.9):

In other words, this code:

for n in range(8, 15 + 1):
    string = '{:.' + str(n) + 'f}';
    print(string.format(f))
    print(string.format(d))

Gives this printout:

0.89999998
0.90000000
0.899999976
0.900000000
0.8999999762
0.9000000000
0.89999997616
0.90000000000
0.899999976158
0.900000000000
0.8999999761581
0.9000000000000
0.89999997615814
0.90000000000000
0.899999976158142
0.900000000000000

Our conclusion is therefore that Java prints decimals with a precision of between 8 and 15 digits by default.

Nice question BTW...

Upvotes: 1

rzwitserloot
rzwitserloot

Reputation: 102902

Pop quiz: Represent 1/3rd, in decimal.

Answer: You can't; not precisely.

Computers count in binary. There are many more numbers that 'cannot be completely represented'. Just like, in the decimal question, if you have only a small piece of paper to write it on, you may simply go with 0.3333333 and call it a day, and you'd then have a number that is quite close to, but not entirely the same as, 1 / 3, so do computers represent fractions.

Or, think about it this way: a float occupies 32-bits; a double occupies 64. There are only 2^32 (about 4 billion) different numbers that a 32-bit value can represent. And yet, even between 0 and 1 there are an infinite amount of numbers. So, given that there are at most 2^32 specific, concrete numbers that are 'representable precisely' as a float, any number that isn't in that blessed set of about 4 billion values, is not representable. Instead of just erroring out, you simply get the one in this pool of 4 billion values that IS representable, and is the closest number to the one you wanted.

In addition, because computers count in binary and not decimal, your sense of what is 'representable' and what isn't, is off. You may think that 1/3 is a big problem, but surely 1/10 is easy, right? That's simply 0.1 and that is a precise representation. Ah, but, a tenth works well in decimal. After all, decimal is based around the number 10, no surprise there. But in binary? a half, a fourth, an eighth, a sixteenth: Easy in binary. A tenth? That is as difficult as a third: NOT REPRESENTABLE.

0.9 is, itself, not a representable number. And yet, when you printed your float, that's what you got.

The reason is, printing floats/doubles is an art, more than a science. Given that only a few numbers are representable, and given that these numbers don't feel 'natural' to humans due to the binary v. decimal thing, you really need to add a 'rounding' strategy to the number or it'll look crazy (nobody wants to read 0.899999999999999999765). And that is precisely what System.out.println and co do.

But you really should take control of the rounding function: Never use System.out.println to print doubles and floats. Use System.out.printf("%.6f", yourDouble); instead, and in this case, BOTH would print 0.9. Because whilst neither can actually represent 0.9 precisely, the number that is closest to it in floats (or rather, the number you get when you take the number closest to 2.0 (which is 2.0), and the number closest to 1.1 (which is not 1.1 precisely), subtract them, and then find the number closest to that result) – prints as 0.9 even though it isn't for floats, and does not print as 0.9 in double.

Upvotes: 1

Related Questions