8

Why java += get wrong result, and how can I prevent this problem? (for example any way show warning in IDE?)

I tried eclipse & IntelliJ but both not show any warning.

Sample code:

    {
        long a = 20000000000000000L;
        double b = 90.0;
        a += b;
        System.out.println(a); // 20000000000000088 NG
    }

    {
        long a = 10000000000000000L;
        double b = 90.0;
        a += b;
        System.out.println(a); // 10000000000000090 OK
    }

    {
        long a = 20000000000000000L;
        double b = 90.0;
        a += (long) b;
        System.out.println(a); // 20000000000000090 OK
    }
andyf
  • 2,928
  • 3
  • 19
  • 35
  • 1
    @Sotirios That question doesn't explain why a number with no fractional portion would have this behavior. – 4castle Jun 02 '16 at 02:59
  • @4castle The precision of `double` doesn't only apply to its fractional portion. – Sotirios Delimanolis Jun 02 '16 at 03:00
  • @SotiriosDelimanolis That is the exact piece of information that question does not contain. [This answer](http://stackoverflow.com/a/2607316/5743988) even *recommends* scaling the numbers to whole numbers because "integer arithmetic in floating-point is exact". – 4castle Jun 02 '16 at 03:00
  • 6
    ***REQUIRED READING***: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – Jim Garrison Jun 02 '16 at 03:05
  • @SotiriosDelimanolis I don't think the question duplicate. It can explain why `0.1 + 0.2 = 0.30000000000000004` but not `10000000000000000 + 90 = 10000000000000088` – andyf Jun 02 '16 at 03:05
  • It's the same question of precision. Try `System.out.println(20000000000000090d);` and you'll see the same thing. Let me try and find a better duplicate. – Sotirios Delimanolis Jun 02 '16 at 03:06
  • @SotiriosDelimanolis and that answer not explain `how can I prevent this problem? (for example any way show warning in IDE?)` – andyf Jun 02 '16 at 03:07
  • Alright, I'll reopen. – Sotirios Delimanolis Jun 02 '16 at 03:07
  • 3
    @andyf The reason for both those behaviors is the same. `0.1` cannot be represented exactly in floating point. Neither can `10000000000000090`. The mantissa doesn't have enough bits. I suppose you could argue that in the case of `0.1` there could never be enough bits but in the second case adding bits would solve the problem. All adding bits does is _move_ the problem. It's always there with numbers of large enough magnitude and precision. – Jim Garrison Jun 02 '16 at 03:08
  • 1
    This question gets asked over and over again. It is a duplicate, maybe not exactly a dup of the question referenced by @SotiriosDelimanolis, but a dup nonetheless. I _know_ it's an exact dup of an answer I wrote a couple of years ago... I'll go find it. – Jim Garrison Jun 02 '16 at 03:10
  • @JimGarrison Thanks for your reference. I think for most coders, they just need a tool (IDE etc) for telling them that not coding like that. I tried eclipse & intellij but both not show any warning. – andyf Jun 02 '16 at 03:14
  • 1
    @andyf The IDEs assume you understand floating point, just like they expect you to understand that integer overflow is silently ignored. Floating point numbers are a powerful idea but they act differently enough from our "intuition" that they must be studied and understood for what they are. – Jim Garrison Jun 02 '16 at 03:18
  • @JimGarrison Thanks for your answer. But, as not all programmers(like me) can always notice this problem, I found a simple way: just forbid `+=` as coding rule. See my answer. – andyf Jun 02 '16 at 05:32
  • That is just plain silly. It has nothing to do with `+=` and everything to do with the fact that floating point has limited precision. You'll have the same problem if you write `a = a + b` instead. – Jim Garrison Jun 02 '16 at 08:14
  • 1
    @JimGarrison but the IDE will show me the compile error while `a=a+b`. – andyf Jun 02 '16 at 08:40

3 Answers3

10

According to the JLS, this compound assignment expression

a += b;

is equivalent to

a = (long) (a + b);

Since b is a double, binary numeric promotion occurs before the addition happens. Both operands are converted to double values and the addition happens with floating point arithmetic.

The value 20000000000000090 cannot be represented exactly as a double and therefore you lose precision, getting 20000000000000088 instead. It then gets cast back to a long which has enough precision to represent 20000000000000088.

Your second snippet produces 10000000000000090 which can be represented exactly as a double.

Your third snippet is equivalent to

a = (long) (a + (long) b);

which uses integer arithmetic. 20000000000000090 can be represented as a long.

I don't know of any tools to warn you about this.

Related:

Community
  • 1
  • 1
Sotirios Delimanolis
  • 252,278
  • 54
  • 635
  • 683
1

Java uses 64-bit IEEE floating point numbers, which use 53 binary digits to represent the significant. In the first example,

{
    long a = 20000000000000000L;
    double b = 90.0;
    a += b;
    System.out.println(a); // 20000000000000088 NG
}

Here the variable a is converted to a double that must use an exponent greater than 1 to represent it, and it will no longer be an exact representation of the value 20000000000000000L.

In this example, it is possible to represent a using only the significand and an exponent of 1.

{
    long a = 10000000000000000L;
    double b = 90.0;
    a += b;
    System.out.println(a); // 10000000000000090 OK
}

In this example, b is explicitly converted to a long, avoiding the need to convert a to a double and perform floating point arithmetic.

{
    long a = 20000000000000000L;
    double b = 90.0;
    a += (long) b;
    System.out.println(a); // 20000000000000090 OK
}
Josh
  • 153
  • 1
  • 7
0

As not all programmer(like me) can understand binary numeric promotion

For preventing such += coding missing, I found a simple way: just forbid all += as coding rule.

I just grep all += in my projects, and change a += b to a = a + b.

IDE shows errors while type not match while + operation, as bellow:

IDE show compile errors

andyf
  • 2,928
  • 3
  • 19
  • 35