Inconsistent behaviour of primitive integer types in Java
Asked Answered
E

3

5

Can someone explain to me like I'm five why I get different behaviour for two of four primitive types representing integers in Java? AFAIK all four are signed and they all use the most significant bit as a sign bit, so why do byte and short behave normally, and int and long act, well, strange? The fragment of oracle docs explaining this would be perfect.

byte a = (byte) (Math.pow(2, 7)-1); //127 - as expected
short b = (short) (Math.pow(2, 15)-1); //32767 - as expected
int c = (int) (Math.pow(2, 31)-1); //2147483647 - as expected
long d = (long) (Math.pow(2, 63)-1); //9223372036854775807 - as expected

a = (byte) (Math.pow(2, 7)); //-128 - as expected
b = (short) (Math.pow(2, 15)); //-32768 - as expected
c = (int) (Math.pow(2, 31)); //2147483647 - why not '-2147483648'?
d = (long) (Math.pow(2, 63)); //9223372036854775807 - why not '-9223372036854775808'?

a = (byte) (Math.pow(2, 8)); //0 - as expected
b = (short) (Math.pow(2, 16)); //0 - as expected
c = (int) (Math.pow(2, 32)); //2147483647 - why not '0'?
d = (long) (Math.pow(2, 64)); //9223372036854775807 - why not '0'?

I'm using Oracle's Java SE 1.7 for Windows. OS is Windows 7 Professional SP1

java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

EDIT, after reading all the answers and tuning my code.
So, to sum up, the only way I found to get expected values is the use of BigInteger. Shift operator works well for bytes, shorts and ints, but when it comes to longs, I cought it on one malfunction.

byte a = (byte) ((1l << 7) - 1); //127 - as expected
short b = (short) ((1l << 15) - 1); //32767 - as expected
int c = (int) (1l << 31) - 1; //2147483647 - as expected
long d = (1l << 63) - 1; //9223372036854775807 - as expected

a = (byte) (1l << 7); //-128 - as expected
b = (short) (1l << 15); //-32768 - as expected
c = (int) 1l << 31; //-2147483648 - as expected
d = 1l << 63; //-9223372036854775808 - as expected

a = (byte) (1l << 8); //0 - as expected
b = (short) (1l << 16); //0 - as expected
c = (int) (1l << 32); //0 - as expected
d = 1l << 64; //1 instead of 0, probably because of the word length limitation      

With BigInteger everything works flawlessly

byte a = (byte) (new BigInteger("2").pow(7).longValue() - 1); //127 - as expected
short b = (short) (new BigInteger("2").pow(15).longValue() - 1); //32767 - as expected
int c = (int) (new BigInteger("2").pow(31).longValue() - 1); //2147483647 - as expected
long d = (new BigInteger("2").pow(63).longValue() - 1); //9223372036854775807 - as expected

a = (byte) (new BigInteger("2").pow(7).longValue()); //-128 - as expected
b = (short) (new BigInteger("2").pow(15).longValue()); //-32768 - as expected
c = (int) new BigInteger("2").pow(31).longValue(); //-2147483648 - as expected
d = new BigInteger("2").pow(63).longValue(); //-9223372036854775808 - as expected

a = (byte) (new BigInteger("2").pow(8).longValue()); //0 - as expected
b = (short) (new BigInteger("2").pow(16).longValue()); //0 - as expected
c = (int) (new BigInteger("2").pow(32).longValue()); //0 - as expected
d = new BigInteger("2").pow(64).longValue(); //0 - as expected

Thanks everyone for big help!

Eoin answered 6/12, 2013 at 22:31 Comment(1)
try 2 << 7, 2 << 15, ... instead, it should be more preciseArmoire
A
6

Section 5.1.3 of the JLS talks about the behavior of the narrowing primitive conversion used by the cast

Otherwise, one of the following two cases must be true:

The value must be too small (a negative value of large magnitude or negative infinity), and the result of the first step is the smallest representable value of type int or long.

The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type int or long.

(emphasis mine)

That is why (int) (Math.pow(2, 32)); becomes Integer.MAX_VALUE and (long) (Math.pow(2, 64)) becomes Long.MAX_VALUE.

Aeromarine answered 6/12, 2013 at 22:36 Comment(1)
Note that that section is specifically talking about floating point to integer vs between integer types.Hydrastis
B
3

Math.pow() returns a double, which is then rounded on conversion to the integral types. The double is evidently rounded a hair under the precision needed to make them overflow.

Barkley answered 6/12, 2013 at 22:33 Comment(0)
H
2

Instructive:

public class PowTest {
    public static void main(String[] argv) {
        double powResult = Math.pow(2.0,31.0);
        int powInt = (int) powResult;
        long powLong = (long) powResult;
        int longInt = (int) powLong;
        System.out.println("Double = " + powResult + ", int = " + powInt + ", long = " + powLong + ", longInt = " + longInt);
    }
}

Result:

C:\JavaTools>java PowTest
Double = 2.147483648E9, int = 2147483647, long = 2147483648, longInt = -2147483648

The double -> int conversion is rounded. The long -> int conversion is truncated.

Hydrastis answered 6/12, 2013 at 22:44 Comment(2)
+1: While reading the excerpt from the JLS rgettman posted, I was thinking about exactly the same test case :)Granvillegranvillebarker
Actually, if I understand correctly, that's the reason i got negative numbers in byte and short. Double is rounded to int, and then truncated to byte and short, right?Eoin

© 2022 - 2024 — McMap. All rights reserved.