Write the bits on paper, think about erasing them from one end, and adding more on the other. Not unlike elementary school and moving the decimal point around when multiplying by 10.
All of your C functions are going to shift zeros in.
So
x = y << 3;
means shift left three bits and the new bits on the right are all zeros. The three bits that were on the left go into the "bit bucket":
x = z >> 2
Lose the two bits on the right and add two zeros on the left.
You will find, and what the K&R exercises are about, the missing functions. Among the processor types out there you have far more shifting capabilities than you do in C or any other high level language.
You have rotate functions where the bit shifted off of one end gets shifted in the other.
So the number 0xD rotated one bit to the right in this manner would be an 0xE, because the least significant bit was a 1 so shifting 1101 to the right, the 1 on the right becomes a 1 on the left 1110.
Sometimes you rotate through the carry bit in the ALU. Let’s say the carry bit had a zero in it and you rotated 0xD one bit 0 1101 leaves 1 0110 a 0x6. Rotate that one more, 0 1011 and you get a 0xB and so on.
Why would you ever rotate through the carry bit you ask? For bigger numbers, say you have four bit registers and want to do an 8 bit shift, let's say each of the letters are bits a bcde fghi where a
is the carry bit and the other two groups of four are four bit registers. Start by rotating the left register through the carry e abcd fghi, and then rotate the right register through the carry
i abcd efgh. Pretty cool; we just did an 8 bit shift with a 4 bit shift function.
If you had cleared the carry bit before starting (often there is an instruction for this or you can always do something like add 0+0 or something else guaranteed to clear that bit) you would have
i 0bcd efgh
which is not unlike what a C shift function would do if you are say on a 32 bit instruction set operating on a 64 bit number.
The processors often have the C like shifts where a zero is shifted in, shift abcd left one gives bcd0 shift abcd right two gives 00ab.
And this leads to some problems with the younger folks with modern processors ... think about such things because an integer divide is both supported on their processor and can operate in a single clock cycle. Back before we had divide or when divide was dozens to hundreds of clocks, but a shift was a single clock you would do all of your power of 2 divides or multiplies using shifting instead. Take the number 0x0D shift that left two you get 0b00001101 << 2 = 0b00110100 or 0x34. 0xD is 13 decimal and 0x34 is 52 decimal. 52 is four times more than 13. Four is 2 to the power of 2. Shifting by two is the same as multiplying by four.
This works both ways; 0x34 shifted right 2 is 0xD, but here is the problem. When you get into negative numbers, take the number minus 4 0xFC, and now divide that by two. Using C 0xFC >> 1 would give 0x7E, but 0x7E is +126 decimal. How does -4/2 = 126?
The problem is that C shifts in zeros. You will find some processors have an arithmetic shift which is different than a logical shift. The arithmetic shift keeps the upper-most bit, so if you are working with a signed number, like 0bQWER, and you arithmetically shifted that right one bit you get 0bQQwe. The upper-most bit both shifts into the next bit and stays where it was.
Shift again 0bQQQW, and so on. Now an arithmetic shift left will shift in zeros and not the least significant bit, so 0bQWER shifted left one is 0bWER0. And that makes sense. -4 shifted left one is 0xF8 which is -8, and -4 times two is -8, so that is right.
So you will find that some processors only have an arithmetic shift right, but not a left. Some allow you to specify an asl, but when they assemble it replace it with an lsl (logical shift left) and who knows some may actually have a separate opcode even though it is the same function. I assume there may be some that have an asl and an asr and lsr, but no lsl.
Just use paper and pencil and figure things out. Start with real numbers as examples, and then go abstract. Want to rotate 0x1234
to the right one bit, let's say?
0001001000110100 write out the bits
x0001001000110100 shift right one
0000100100011010 because this is a rotate fill in the new bit with the bit that fell off on the prior operation
Want to now shift two bits to the right
0000100100011010
xx0000100100011010
1000001001000110
How would I do a single bit rotate in C?
unsigned int rotate_right_one ( unsigned int x )
{
unsigned int y;
y = x & 1; // Save the bit that is about to fall off the right
x >> = 1; // Logical rotate one bit
x |= y<<31; // This assumes that unsigned int is 32 bits.
return(x);
}
To rotate more you can simply call this function multiple times or think about the mask and shift above and how would that work for more than one bit.
Also note that some processors only have one rotate function. For example, think about this. I have a four bit register and I rotate 5 bits. What do I get?
abcd
bcda first rotate
cdab second
dabc third
abcd fourth
bcda fifth
What does a single rotate left look like?
abcd
bcda one bit left.
Five right on a four bit register is the same as one left 5-4=1. Like the asl, some processors will let you code the operation, but the assembler replaces that operation with the other rotate using nbits-shift as the rotate amount.
For some people logical bit operations are as tough as pointers to understand, but it is a fundamental and if you learn it and use it you will be way out ahead of your competition or those around you.
Here is an example that counts the number of bits in some variable:
for(count=0, r=1; r; r<<=1)
if(r&some_variable)
count++;
Understand that line of code and you are well on your way to both learning C and the logical bit operations.