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): byte(8-bit), short(16-bit), int(32-bit), long(64-bit)
  • 2 types for decimals (signed): float(32-bit), double(64-bit)
  • 1 type for characters (not signed): char(16-bit)
  • 1 type for booleans: boolean

In this post we are going to leave out of the discussion the boolean type.

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 -128 to 127, extremes included).

char and 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 char variable.

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 byte?

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 -56!

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 200 as a byte
  • The byte maximum possible value is 127 (plus the 0), so a total of 128 possible non-negative values
  • 200-128=72
  • 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