Linux C Programming Tutorial Part 15 - 2's Complement and Negative numbers

Up until now, in this ongoing C programming tutorial series, we have discussed quite a few concepts, but missed a basic one. It's about negative numbers. Yeah, though we briefly mentioned signed vs unsigned variables in one of our initial tutorials, we didn't actually discuss how negative numbers are stored in memory.

Well, that's exactly what will be discussed in this tutorial. So without further ado, let's start with the discussion.

2's complement

Before we begin with the explanation on negative numbers representation in memory, it's important we know the concept of 1's and 2's complement, both of which are binary level operations.

Let's take a very simple example. Suppose you have a 4 byte integer 'a' with decimal value 15. Then here's how it is represented in binary form memory:

00000000 00000000 00000000 00001111

Now, to calculate the one's complement, just invert all the bits. So following is the 1's complement representation of 15:

11111111 11111111 11111111 11110000

Now, if you add 1 to the above binary representation, you get the 2's complement.

11111111 11111111 11111111 11110001

So the above representation is the two's complement of 15.

Negative numbers

Now, some of you must be thinking why we discussed 1's and 2's complement? Well, the answer lies in the fact that the binary representation of a negative number is calculated through 2's complement.

Hard to believe? Here's the proof:

The 2's complement we calculated in the previous section can be represented in hexadecimal form as 0xFFFFFFF1. Now, let's see what this value is in decimal form through a C program

Here's the code:

#include <stdio.h>

int main()
{
int a = 0xFFFFFFF1;
printf("a = %d", a);

return 0;
}

And the following is the output:

a = -15

Believe it now? We started with a number '15', calculated its 2's complement, and when we converted the two's complement value again into decimal, we found it's -15. 

Moving on, now let's slightly tweak the code to make sure the printf call reads the value of variable 'a' as as an unsigned integer.

#include <stdio.h>

int main()
{
int a = 0xFFFFFFF1;
printf("a = %u", a);

return 0;
}

 Here's the output now:

a = 4294967281

Oops, the output changed, and now it's a huge positive value. But why this happened? Isn't 0xFFFFFFF1 the 2's complement of 15 as we saw earlier? 

Yes, 0xFFFFFFF1 is two's complement of 15, but if you don't look it from that perspective, it is also a normal value (4294967281). The difference lies in how it is read. If it is read as a signed integer (through %d in printf), you'll see the output as -15, but if it is read as an unsigned integer (through %u in printf), you'll see the output as 4294967281.

As a rule of thumb with signed variables (which deal with both negative and positive values), keep in mind that binary representation of negative numbers always has '1' as the leftmost bit, while in case of positive numbers the bit in question is always 0.  

Finally, note that you can also reverse a two's complement representation to get its positive counterpart. As an example, let's again take the 0xFFFFFFF1 value, which is hex representation of -15. It is represented in binary form as:

11111111 11111111 11111111 11110001

Now, to get its positive counterpart, just perform a 2's complement again. Which means, first do a 1's complement:

00000000 00000000 00000000 00001110

And then add 1

00000000 00000000 00000000 00001111

Now, if you convert this, you'll get value 15 in decimal form. 

Conclusion

I hope this tutorial helped you understand the concept of negative numbers in context of how they are represented in memory. I suggest you try out the examples we used in this tutorial, and in case you encounter any problem, or you have any doubt or query, drop us a comment below.

Share this page:

1 Comment(s)