Varying Behavior for Possible Loss of Precision

Varying behavior for possible loss of precision

That's because b += 1.0; is equivalent to b = (int) ((b) + (1.0));. The narrowing primitive conversion (JLS 5.1.3) is hidden in the compound assignment operation.

JLS 15.26.2 Compound Assignment Operators (JLS Third Edition):

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

For example, the following code is correct:

short x = 3;
x += 4.6;

and results in x having the value 7 because it is equivalent to:

short x = 3;
x = (short)(x + 4.6);

This also explains why the following code compiles:

byte b = 1;
int x = 5;
b += x; // compiles fine!

But this doesn't:

byte b = 1;
int x = 5;
b = b + x; // DOESN'T COMPILE!

You need to explicitly cast in this case:

byte b = 1;
int x = 5;
b = (byte) (b + x); // now it compiles fine!

It's worth noting that the implicit cast in compound assignments is the subject of Puzzle 9: Tweedledum from the wonderful book Java Puzzlers. Here are some excerpt from the book (slightly edited for brevity):

Many programmers think that x += i; is simply a shorthand for x = x + i;. This isn't quite true: if the type of the result is wider than that of the variable, the compound assignment operator performs a silent narrowing primitive conversion.

To avoid unpleasant surprises, do not use compound assignment operators on variables of type byte, short, or char. When using compound assignment operators on variables of type int, ensure that the expression on the right-hand side is not of type long, float, or double. When using compound assignment operators on variables of type float, ensure that the expression on the right-hand side is not of type double. These rules are sufficient to prevent the compiler from generating dangerous narrowing casts.

For language designers, it is probably a mistake for compound assignment operators to generate invisible casts; compound assignments where the variable has a narrower type than the result of the computation should probably be illegal.

The last paragraph is worth noting: C# is a lot more strict in this regard (see C# Language Specification 7.13.2 Compound assignment).

Understanding possible loss of precision required char found byte

The error text is misleading.

A char is a 2 byte unsigned type (range 0 to 65535)

A byte is a 1 byte signed type (range -128 to 127).

Therefore a byte cannot be represented in a char in full generality (as you'll lose the negatives). So you get an error; albeit a misleading one.

possible loss of precision is Java going crazy or I'm missing something?

Actually all logic operatos (& | ^) return an int, regardless of their operands. You have to cast the final result of x|y as well.

possible loss in precision for byte

byte c= a+b;, the + operator here will return an int ,that's why you need to use a cast.

What is causing this possible loss of precision error?

I couldn't find the exact reason about this in JLS, so I went through the byte code and found that the reason is that the compiler couldn't inline the value of i in the second case, but is able to do it in the first case.

Here's the code:

final int x = 90;
System.out.println(x);

final int i;
i = 90;
System.out.println(i);

The compiled byte code looks like:

 0: getstatic     #2        // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 90
5: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
8: bipush 90
10: istore_2
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_2
15: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
18: return

So in the first case (3 to 5), it directly uses the value 90 for printing, while in the second case (8 to 15), it has to store the value into the variable and then load it back onto the stack. Then the print method will pick the top value of the stack.

So, in case of the assignment:

byte x = i;

The value of i will be picked up from the stack at runtime, and not inlined by the compiler. So the compiler doesn't know what value i might contain.

Of course, this is all my guess. The byte code might be different on different JVMs. But I've the strong intuition that this might be the reason.

Also, JLS §4.12.4 might be relevant here:

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28), is called a constant variable.

Since in the second case, the variable is not initialized by a constant expression, but is later assigned a value, it is no longer a constant variable.

Math.random() and precision loss curiosity

Assigned operators like += do an implicit cast.

Note: in this case Math.random() will be rounded down to 0 every time which is a significant loss of precision. ;)

However Math.random() + 1 has a very small chance of being rounded to 2. e.g. 1.999999 will be rounded to 1 but 1.9999999999999999 will be rounded to 2 (but the double + operator rather than the cast to int).

long l = Double.doubleToLongBits(1.0);
double d0_999etc = Double.longBitsToDouble(l -1);
System.out.println("The value before 1 is " +d0_999etc+" cast to (int) is "+ (int) d0_999etc);
System.out.println("The value before 1, plus 1 is " +(1+d0_999etc)+" cast to (int) is "+(int)(1 +d0_999etc));

prints

The value before 1 is 0.9999999999999999 cast to (int) is 0
The value before 1, plus 1 is 2.0 cast to (int) is 2

Why am I getting a warning about Possible loss of precision in Java?

Because when you assign a numeric literal, it defaults to Integer. The compiler doesnt inspect the value to check the precision will not be lost.

Additionally, Java will then perform integer arithmetic. See Primitive type 'short' - casting in Java for more information on short values.



Related Topics



Leave a reply



Submit