Ignore everyone who is telling you that the top bit is a sign bit. That is the wrong way to think about it.
The right way to think about it is:
- We have 65536 possible bit patterns.
- Therefore we can represent 65536 possible numbers.
- We must have a map that assigns meaning to each bit pattern.
For unsigned shorts, we assign bit patterns as follows:
0000000000000000 --> 0
0000000000000001 --> 1
...
0111111111111111 --> 32767
1000000000000000 --> 32768
1000000000000001 --> 32769
...
1111111111111111 --> 65535
For signed shorts, we use the following convention:
0000000000000000 --> 0
0000000000000001 --> 1
...
0111111111111111 --> 32767
1000000000000000 --> -32768
1000000000000001 --> -32767
...
1111111111111111 --> -1
Simple as that.
Why do we use this convention?
Three reasons:
(1) The first 32K values are the same whether you are signed or unsigned. That is very convenient.
(2) In both conventions, "all zero bits" means zero.
(3) Because addition works exactly the same in both conventions!
We have
0000000000000000 --> 0
and we want to add one. We add one using binary rules and we get:
0000000000000001 --> 1
This works whether the short is signed or unsigned.
We have unsigned short:
1000000000000000 --> 32768
We wish to add one. We do so using binary rules, and we get the right answer:
1000000000000001 --> 32769
Same for signed shorts. We have
1000000000000000 --> -32768
and we wish to add one. We do so with binary rules and we get:
1000000000000001 --> -32767
Similarly, you can verify that by adding 1111111111111111 to any binary number, you get "one less", and therefore subtraction of one works as well as addition of one. You can then go on to show that in general, addition and subtraction work the same in both signed and unsigned arithmetic, which means the processor doesn't have to know whether the compiler thinks the values are signed or unsigned.
That is why we use twos complement: because the underlying math is exactly the same whether you are doing signed or unsigned arithmetic.
Notice that I said nothing whatsoever about a "sign bit" in there. The fact that the high bit is set for negative numbers is just a nice bonus. The real property we want is to only have to build hardware to do math once.
Twos complement is just a convention for assigning one of two possible meanings to bit patterns based on the type associated with the variable holding that bit pattern. As an industry, we chose this convention because it is cheap to make high performance hardware that uses this convention.
There are a great many other conventions that we could have chosen. For example, we could have said that for signed numbers we use the map:
0000000000000000 --> -32768
0000000000000001 --> -32767
...
0111111111111111 --> -1
1000000000000000 --> 0
1000000000000001 --> 1
...
1111111111111111 --> 32767
Notice that this is exactly the same as before except for the top bit!
In this interpretation, addition still works as we expect. But this map does not have the property that the first 32K values are the same between short and ushort, and it does not have the property that all zero bits means zero. So we do not use this convention, because it is less convenient. In this convention, converting from short to ushort would require doing an addition. Setting a short to zero requires setting the bytes to something other than zero. We could use a convention where the signed numbers were "in order", we just don't because it is painful to do so.