Bits of Java – Episode 4: Overflow and Underflow
This week I would like to talk about what happens when you force a value, which is outside the range of a certain primitive type, to be of that type.
That sounded like a tongue-twister, didn’t it? Let me remind a few concepts before. You are probably all familiar with the fact that Java has eight primitive types:
- 4 types for integers (signed):
- 2 types for decimals (signed):
- 1 type for characters (not signed):
- 1 type for booleans:
In this post we are going to leave out of the discussion the
As you can see, I have also specified the space in bits which is allocated in memory when you define a variable of that type. This also says which is the range of values that can be covered by a certain type.
For instance, the
byte type is used to describe signed integers values and it has 8-bits for doing it, meaning it can take 28 different values. Since it needs to describe also negative values (plus
0, of course), the range of the possible values for a
byte variable is
[-128; 127] (this notation means from
127, extremes included).
short are strongly related, since they both allocate 16-bits of space. The main difference here is that
short represents signed values, so it needs to allocate some space for representing negative values as well as positive ones, while
char can only take non-negative values, resulting in a larger set of positive numbers that can fit inside a
Now, when we want to fit a smaller type into a larger one there is no need for casting. Java can do that by itself. On the other hand, if you want to fit a larger type into a smaller one, you have to explicitly tell that to the compiler, by casting it.
short number = 23; //OK: we are passing from a smaller to a larger type int larger_number = number; System.out.println(larger_number); //prints 23 //DOES NOT COMPILE: we are passing from a larger to a smaller type byte smaller_number = number; //OK: we are passing from a larger to a smaller type but we are casting byte smaller_number_casting = (byte) number; System.out.println(smaller_number_casting); //prints 23
Now, what would have happened if
number, instead of having a value of
23, like in the example above, had a value of, let’s say,
200, meaning a value which is outside the range of possible values for
short number = 200; //OK: we are passing from a smaller to larger type int larger_number = number; System.out.println(larger_number); //prints 200 //OK: we are passing from a larger to a smaller type but we are casting byte smaller_number_casting = (byte) number; System.out.println(smaller_number_casting); //prints -56
The first print statement will print the value of
200, since this is a perfectly valid value for the range covered by an
int variable. What’s interesting here is the second print statement. This will output the value
What’s going on here? We started from a value of
200 and we ended up with
-56. Well, we are casting
number to be a
byte, and this is OK, but its value is still outside the range for a
byte, so it cannot be represented as
200. What the compiler does is to start from the lowest value of the
byte range (
-128) and counting up to fill the difference between the value it has to represent and the maximum possible value it actually can represent. The reasoning steps are the following:
- We want to represent a value of
bytemaximum possible value is
0), so a total of
128possible non-negative values
- Then the compiler starts from the lowest representable value and counts up the difference: -128+72=-56
And the mystery is resolved! This process is called overflow, meaning we are trying to represents a value which is too large with respect to the allowed range, and then it starts back from the lowest to fill the difference. The opposite is what is called underflow.
So, just remember that, once you cast, the compiler may be happy with the types of your variables, but the resulting values can be different from what you expected!
Stay tuned for our next post: Numeric Promotion.
by Ilenia Salvadori