In Java why number operations' result is different from variable operations

Viewed 164

I have the following code:

System.out.println("(int) (10 * 0.75 / 0.6 + 0.5)=" + (int) (10 * 0.75 / 0.6 + 0.5));

The print out is: (10 * 0.75 / 0.6 + 0.5)=13

and another one:

int a = 10;
float b = 0.75f;
float c = 0.6f;
System.out.println("(int) (a * b / c + 0.5)=" + (int) (a * b / c + 0.5));

The print out is (a * b / c + 0.5)=12

Why they are different?

EDIT:

System.out.println("(int) (10 * 0.75f / 0.6f + 0.5f)=" + (int) (10 * 0.75f / 0.6f + 0.5f));

The print out is 12 now: (int) (10 * 0.75f / 0.6f + 0.5f)=12

3 Answers

Your variables are assigned the float values 0.75f and 0.6f, your inline expression uses the double values 0.75 and 0.6.

You will get the same result if you use the same types for your values, so either 0.…f in your inline expression or making your variables double values.

Doubles can store 64 bits (1 sign bit, 11 bits exponent and 53 bits significand) of precision, floats or Singles only 32 bits (1 sign bit, 8 bits exponent and 24 bits significand). The difference in the result is explained by rounding errors.

Read up on how floating point numbers are stored in a Computer on Wikipedia:

Double-precision binary floating-point is a commonly used format on PCs, due to its wider range over single-precision floating point, in spite of its performance and bandwidth cost. As with single-precision floating-point format, it lacks precision on integer numbers when compared with an integer format of the same size. It is commonly known simply as double. The IEEE 754 standard specifies a binary64 as having:

Sign bit: 1 bit
Exponent: 11 bits
Significand precision: 53 bits (52 explicitly stored)

The sign bit determines the sign of the number (including when this number is zero, which is signed).

The exponent field can be interpreted as either an 11-bit signed integer from −1024 to 1023 (2's complement) or an 11-bit unsigned integer from 0 to 2047, which is the accepted biased form in the IEEE 754 binary64 definition. If the unsigned integer format is used, the exponent value used in the arithmetic is the exponent shifted by a bias – for the IEEE 754 binary64 case, an exponent value of 1023 represents the actual zero (i.e. for 2e − 1023 to be one, e must be 1023). Exponents range from −1022 to +1023 because exponents of −1023 (all 0s) and +1024 (all 1s) are reserved for special numbers.

@knittl's answer is correct, and fully explains the behaviour you observe. That said, in case anyone arrives at this question and their code doesn't use the less precise float type, there is another more subtle reason that similar code might produce different results, so I think it is worth adding another answer about that.

In the code which doesn't use variables, (int) (10 * 0.75 / 0.6 + 0.5) is a constant expression (JLS §15.28), so it is evaluated at compile-time, not at runtime; and constant expressions are always FP-strict (JLS §15.4) meaning they may be evaluated with a different floating-point value set to non-FP-strict expressions, and that means sometimes they can have different results. Of course, the cast to int afterwards can amplify a small difference into a whole-number difference.

The two versions compile to significantly different bytecode:

  public void withVars();
    Code:
       0: bipush        10
       2: istore_1
       3: ldc           #2                  // float 0.75f
       5: fstore_2
       6: ldc           #3                  // float 0.6f
       8: fstore_3
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: iload_1
      13: i2f
      14: fload_2
      15: fmul
      16: fload_3
      17: fdiv
      18: f2d
      19: ldc2_w        #5                  // double 0.5d
      22: dadd
      23: d2i
      24: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
      29: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      32: return

  public void withoutVars();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #9                  // String (int) (10 * 0.75 / 0.6 + 0.5)=13
       5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

Note that the version with variables does the arithmetic at runtime: the fmul, fdiv, dadd and d2i operations do the multiplication, division, addition and cast to int respectively. However, the version without variables simply has a ldc (load constant) instruction, and this constant is actually the whole string "(int) (10 * 0.75 / 0.6 + 0.5)=13" because even the string concatenation part is a constant expression, so not even the string concatenation is done at runtime.

As a side-note, if you use variables but declare them final, then the expression becomes a constant expression again, so it will compile to the same bytecode as the one without variables.

The difference is that you're using double type accuracy (0.75 is a double, 0.75f is a float) in the first calculation, but float type accuracy in the second.

When you're changing all "float" to "double" in your second attempt the results are of course the same.

Related