In a recent question on the site, a meme about certain expressions in a programming language is shown, asking how one of them works.
The meme was the following:
The question was how it worked i -=- 1
. However, the one that really caught my attention was the last one: i = ~-i
does this really serve the same purpose as the previous ones, that is, to increase i
by 1?
If so how does it work?
Actually the expression is wrong. If you test it, for example in a Python interpreter, you will see that it does not increment
i
, but decrements it:This is already quite mysterious anyway, how is it that it decrements
i
?The reason is that a sign change is implemented in the architecture by what is called a "2's complement", and the operator
~
performs a "1's complement". Both operations combined result in the decrement, as we will see.2's complement
The logic behind "2's complement" is to be able to do subtraction using the same machinery that you have for doing addition. Imagine that your CPU has only 8 bits (it's actually 32 or 64, but let's keep the example manageable), and therefore the only numbers it can handle are from 0 to 255. Also imagine that this CPU only knows addition (what which is quite close to reality).
Now imagine that we have the number 2 and we want to subtract 1 from it. Since the CPU only knows how to add, how could we subtract?
The trick is to realize that having only 8 bits, if a result goes over 255, it somehow "flips the marker" and starts over at zero. So if instead of subtracting 1 from 2, we add 255, the result would be 257, but this does not fit in 8 bits. The remaining bit (the most significant) is discarded, and the remaining bits would remain encoding 1 (257-256).
In binary you can see:
The leftover (carry) bit is not part of the response, which would be
00000001
. Therefore we have added a quantity to 2 and 1 has come out. That quantity therefore has to be -1, right? So we choose that 255 actually represents -1.This can be done for any positive N between 1 and 127. Make it negative by changing it to (256-N) and this is called a "2's complement".
So the 2's complement of a number N is 256-N (on 8-bit CPUs, in general, with M bits it would be 2 M -N). And that's what the operator
-
in front of an integer does, get its 2's complement.So
-i
compute the 2's complement ofi
.It turns out that there is another, simpler way to calculate this 2's complement if we look at it in binary. We saw that the 2's complement of 1 was 255, which in binary looks like this:
If you compare both you see that C-2 has been obtained by changing all 0's to 1's, except the last one. Actually there is a general rule of how to calculate it:
This method works for any number, not just -1. And for any number of bits.
Explanation of the expression
And we already have the ingredients to know how the expression works:
First, due to
-i
the 2's complement of is calculatedi
, which consists of changing all 1's to 0's and all 0's to 1's, and finally adding 1 to the result.Then it is applied
~
to the result. This operator does logical negation, or "1's complement". That is, it changes all 1's to 0's and 0's to 1's again (except then it doesn't add 1 to the result).Let's see how it works assuming that
i
it is equal to 10 (which in binary and with 8 bits is00001010
)And it turns out that what comes out is 9, one less than the initial value.
What if I want to increase?
Once the mechanism is understood, we see that to increase it is enough to use those same operators but in reverse order:
Here it is even easier to explain how it works:
~
which changes the 1s to zeros and zeros to 1.