15

Whats wrong with this simple 'double' calculation in java?

I know some decimal numbers can not be represented in float / double binary formats properly, but with the variable d3, java is able to store and display 2.64 with no problems.

double d1 = 4.64;
double d2 = 2.0;
double d3 = 2.64;
double d4 = d1 - d2;

System.out.println("d1      : " + d1);
System.out.println("d2      : " + d2);
System.out.println("d3      : " + d3);
System.out.println("d4      : " + d4);
System.out.println("d1 - d2 : " + (d1 - d2));

Answer,

d1      : 4.64
d2      : 2.0
d3      : 2.64
d4      : 2.6399999999999997
d1 - d2 : 2.6399999999999997
vi.su.
  • 605
  • 2
  • 9
  • 19
  • 3
    There's absolutely `nothing wrong` with this. – The Dark Knight May 23 '13 at 06:55
  • 6
    I think people get offended by the term "what's wrong?". I just mean, "whats going on here?":) – vi.su. May 23 '13 at 06:58
  • @vi.su. Yes I am also waiting for the answer :) :) – Grijesh Chauhan May 23 '13 at 07:08
  • 3
    @TheDarkKnight Actually, there is something wrong. The computer gives you an answer that's wrong, even to the precision of the machine. (We know this since the machine has a more accurate representation of the correct answer.) Yes, we all know about this behavior and guard against it, but saying "The computer is wrong," is an accurate statement. – jpmc26 May 23 '13 at 07:25
  • may be something with '64', because it is 2^6 (1000000). for the same calculation above, If I use '4.63' then, answer is as expected. again, for '4.32' answer is '2.3200000000000003'.. :) – vi.su. May 23 '13 at 07:34
  • I don't think it's related to the 64 or 32. See my answer about errors. – jpmc26 May 23 '13 at 07:39
  • 4
    @Raedwald I don't think it's a duplicate. The focus of the question is different. In the question you link, the OP asks how to fix it. This question asks about the cause. Definitely closely related, though. – jpmc26 May 23 '13 at 07:51
  • Guys, this is not a duplicate. I'm asking a specific case where java is able to store and print a number properly, but not able to use it in calculations. @raedwald – vi.su. May 30 '13 at 07:08
  • 2
    @vi.su java is using the number properly, remember if your program relys on double numbers being precise then you need to rethink your program. 0.64 just isnt a number in binary, 64/100 cannot be expressed exactly in binary, much like 2/3 cannot be expressed exactly in decimal – Richard Tingle Aug 03 '13 at 18:39

5 Answers5

18

The problem

In binary 2.64 is 10.10100011110101110000101000111101 recurring, in other words not exactly representable in binary, hence the small error. Java is being kind to you with d3 but as soon as actual calculations are involved it has to fall back on the real representation.

Binary Calculator

Further more:

2.64= 10.10100011110101110000101000111101
4.64=100.1010001111010111000010100011110 

Now, even though the .64 is the same in both cases, it is held to different precisions because the 4=100 uses up more of the double significant figures than 2=10, so when you say 4.64-2.0 and 2.64 the .64 is represented with a different rounding error in both cases, this lost information cannot be recovered for the final answer.

N.B. I'm not using the double number of significant figures here, just whatever the binary calculator would produce, however the effect is the same whatever the number of significant figures

Never assume double values are exact (although their inaccuracies are microscopic and caused only because certain numbers can't be exactly expressed in binary).


Floating point numbers aren't exact, but only from a decimal point of view

While you should always expect that doubles will have small errors for the last few decimal places it would be wrong to think of binary representations as "bad" or worse that decimal.

We are all used to certain numbers (like 1/3 for example) not being exactly representable in decimal and we accept that such a number will end up as 0.333333333333 rather than the true value (which I cannot write down without infinite space); it is within that context that binary numbers cannot be exactly expressed. 1/10 is such a number that cannot be exactly expressed in binary; this suprises us only because we are used to decimal

Community
  • 1
  • 1
Richard Tingle
  • 15,728
  • 5
  • 47
  • 71
  • 3
    Awesome actual answer. – jpmc26 May 23 '13 at 07:46
  • 100s thanks Richard!! :) – Grijesh Chauhan May 23 '13 at 08:03
  • @RicharTingle Can also tell me why [**my C code**](http://codepad.org/rhy0jSwi) behave differently then Java. – Grijesh Chauhan May 23 '13 at 08:07
  • 1
    @GrijeshChauhan I think that the printf statement has used a default rounding of 6 decimal places. Rounded to that precision 2.6399999999999997 is 2.640000. Java defaults to show the whole decimal representation. Its usually only in the last 1 or 2 decimal places that the problem appears so if there is any rounding at all the error is hidden – Richard Tingle May 23 '13 at 08:12
  • @RichardTingle Excellent Richard!, Thanks again for your helpful comment!! – Grijesh Chauhan May 23 '13 at 08:27
  • +1, however,"never assume" seems too extreme :) Double values are exact if and only if they are exactly representable as double. IEEE 754 and Java spec guarantees that. 2.0, 2.25, 2.125 are exact, 2.25 + 2.125 is _exactly_ 4.375, etc. – user396672 May 23 '13 at 08:56
  • 1
    @user396672 True, but a calculation that should give 2.25 as its end result but has any step in it that is inexact could be inexact itself (eg 2.26-0.01 approximately equals 2.25). Admittedly if you could somehow guarantee that you're using only binary friendly decimals the whole way through you could assume that. But I can't think of any reasonable application in which that would be true. – Richard Tingle May 23 '13 at 09:10
4

d1 - d2 returns the exact result of binary float arithmetic and it is 2.6399999999999997 and so it is printed. If you want to round it, you can do it during printing

System.out.printf("d1 - d2 : %.2f",  d4);

or with Commons-Math

d4 = Precision.round(d4, 2);
Evgeniy Dorofeev
  • 124,221
  • 27
  • 187
  • 258
  • 2
    rounding is not an issue. please check the variable d3. – vi.su. May 23 '13 at 07:13
  • Thanks that helped a lot. This is what I have 11.6 + 76.8 + 11.6 and keeps returning 99.999.... all the time. instead of 100 which my calculator does. – theo231022 Jul 20 '17 at 09:21
3

Mainly because of the fact that double is a double-precision 64-bit IEEE 754 floating point. It's not meant for keeping exact decimal values. That's why doubles are not recommended for exact calculations. Use the String constructor of BigDecimal instead, like:

new BigDecimal("2.64")
Erik Pragt
  • 11,804
  • 10
  • 44
  • 55
3

It's because the errors in the internal representations of 4.64 and 2.0 combine constructively (meaning they make a larger error).

Technically speaking, 2.64 isn't stored exactly, either. However, there is a particular representation that corresponds to 2.64. Think about the fact that 4.64 and 2.0 aren't stored exactly, either, though. The errors in 4.64 and 2.0 are combining to produce an even larger error, one large enough that their subtraction does not give the representation of 2.64.

The answer is off by 3*10^-16. To give something of an example of how that can happen, let's pretend the representation for 4.64 is 2*10^-16 too small and the representation for 2.0 is 1*10^-16 too large. Then you would get

(4.64 - 2*10^-16) - (2.0 + 1*10^-16) = 2.64 - 3*10^-16

So when the calculation is done, the two errors have combined to create an even bigger error. But if the representation for 2.64 is only off by 1*10^-16, then this would not be considered equal to 2.64 by the computer.

It's also possible that 4.64 just has a larger error than 2.64 even if 2.0 has no error. If 4.64's representation is 3*10^-16 too small, you get the same thing:

(4.64 - 3*10^-16) - 2.0 = 2.64 - 3*10^-16

Again, if the representation of 2.64 is only off by 1*10^-16, then this result would not be considered equal to 2.64.

I don't know the exact errors in the real representations, but something similar to that is happening, just with different values. Hope that makes sense. Feel free to ask for clarification.

jpmc26
  • 23,237
  • 9
  • 76
  • 129
0

Nothing wrong with it. But try using BigDecimal

http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html

Note: double and float are internally represented as binary fractions according to the IEEE standard 754 and can therefore not represent decimal fractions exactly

Scary Wombat
  • 41,782
  • 5
  • 32
  • 62
  • my vote is pending till you explain the answer :) – Grijesh Chauhan May 23 '13 at 06:58
  • 2
    BigDecimal for 2.64 ?? :) – vi.su. May 23 '13 at 07:00
  • Because double and float are internally represented as *binary* fractions according to the IEEE standard 754 and can therefore not represent decimal fractions exactly. http://mindprod.com/jgloss/floatingpoint.html http://www.math.byu.edu/~schow/work/...atingPoint.htm – Scary Wombat May 23 '13 at 07:00
  • @vi.su Yes. Is it possible you are confused with BigInteger? – Erik Pragt May 23 '13 at 07:01
  • @ErikPragt, no. when it is able to store and use variable d3, why it is not working for the same value from some other calculation? – vi.su. May 23 '13 at 07:04
  • @user2310289 + for nice links ... – Grijesh Chauhan May 23 '13 at 07:06
  • @vi.su some floating point numbers can be presented exactly and same can not. – Scary Wombat May 23 '13 at 07:06
  • @user2310289 It's true that some floating point numbers can be represented exactly and some can't, but that doesn't seem to be what we're dealing with. In this question, a calculation results in an answer that's less accurate than the most accurate internal representation of the correct result. Also, I this vi.su. was implying that using `BigDecimal` for such a small number seems strange. – jpmc26 May 23 '13 at 07:43
  • @jpmc26 You are absolutely correct and your explanation below is great. But the question was "Whats wrong with this simple 'double' calculation in java?" – Scary Wombat May 23 '13 at 07:47