Understanding the primitive numeric type conversions in Java
How difficult is to convert from long number to integer (int) in Java? Seems very simple - just do “(int)” and that's all. How many times we blamed the irresponsible API designers using int type for the timeout value (in milliseconds) here and long value in other places, and so on? This subject is very simple by its nature but it does require some understanding of the internal representation of the the numbers in Java virtual machine. We will try not to go too deep into details to keep it simple
Why it is important to understand how does it work? Remember: Java is a great programming language that combines high-level OOP features and low-level primitive arithmetic operations. Consider the following example:
long a = 4294967295L; System.out.println("long a = " + a); int b = (int)a; System.out.println("int b = " + b);
What kind of output do you think it produces?
long a = 4294967295 int b = -1
Do not rush submitting a bug to Sun. This is correct. And this is quite unfortunate if you do not expect this kind of conversion result - you may get a negative number or even 0 instead of a relatively large positive number. Let me explain why.
In JVM, just like in the real computer the integer numbers are stored in 8, 16, 32 and 64-bit binary words. And the most significant bit is dedicated to the sign. For example (for simplicity we will be looking at the 8-bit numbers):
Bit no | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | = 56 (decimal) |
Bit value | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 |
Bit no | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | = -72 (decimal) |
Bit value | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 |
First example is clear - we have a positive number 56, you can use almost any calculator that supports the binary numbers to verify that. Second example is more tricky. If you convert the binary number 10111000 into decimal system you would get 184. However, in JVM it is -72. The most significant bit is set to 1 which means that the number is negative. The rest (0111000=56 dec) is the two's complement of 72 (128-56=72). This is how the negative integer numbers are stored. The same rule applies to the longer numbers.
Now lets understand what happens when you cast a long to int (or short to byte etc). In Java there are two kinds of conversions that apply to the integer numbers:
- Widening primitive conversions
- Narrowing primitive conversions
When we have a case of widening conversion, we convert a number to the representation that uses more bits. Narrowing conversion is opposite - we convert to a number that uses fewer bits than the original one.
Widening conversions are simple. For unsigned values (e.g. char) the extra space gets zeroed. For signed values the sign is preserved in the most significant bit and the rest is set to extended two'-complement value. When widening the integer numbers no precision loss happens, but in case of conversion to the floating point type it is possible. The following conversions are widening ones:
- byte to short, int, long
- short to int, long
- char to int, long
- int to long
As you can imagine, going back is more difficult. In case of narrowing conversion from long (64 bit) to int (32) bit we have 32 fewer bits in the destination number. In fact, the narrowing conversion just discards the most significant bits of the original number leaving the lowest part of it. And it does not do anything special about the sign. Thus:
short s = 696; // 0000 0010 1011 1000 byte x = (byte)s; System.out.println("byte x = " + x);
Produces:
byte x = -72
Now you should understand why - because when we narrow short down to the byte the JVM discards the most significant part (00000010) and the result (in binary form) is 10111000. This is the same number we were looking at before. And, as you can see, it is negative, unlike the original value.
The following integer type conversions are the narrowing ones:
- short to byte or char
- char to byte or short
- int to byte, short, or char
- long to byte, short, char, or int
Now you should better understand the consequences of the operations that appear so harmless from the first glance. And, if you really want to be protected from the inappropriate values passed to your code from outside, you must either put some extra conditions or do something like this:
long v = .....; int i = (int)Math.min(v, Integer.MAX_VALUE);
A little note: in Java the conversion from byte to char is widening and narrowing at the same time. The reason is: the byte gets first converted to the int and then the int gets converted to the char.
blog comments powered by Disqus