The practice problems below cover base conversion, bitwise operators, and constructing bitmasks. Reveal the answers for each section to double-check your work. Please ask questions about anything you don't understand!
A few miscellaneous notes about bit operations as you practice further:
- operator precedence with bit operators and other operators can be tricky. Always use parentheses where precedence is ambiguous just to make sure operators execute in the order you expect. For instance,
1<<2 + 3<<4
means1 << (2+3) << 4
due to precedence rules. Writing(1<<2) + (3<<4)
ensures the correct order. - put a
U
after a number literal to make it unsigned. For instance,1U
means the literal1
as an unsigned number. - put an
L
after a number literal to make it along
(64 bits) instead of anint
, which it is by default. This highlights a common issue! If you want, for instance, a long with the index-32 bit on and everything else off, the following does not work:
long num = 1 << 32;
This is because the 1
is by default a signed int
, and you cannot shift a signed int
by 32 spaces because it has only 32 bits. Instead, you must specify that the literal be a long
:
long num = 1L << 32;
With this material and the other material from the past lectures, test your understanding with the practice below.
Base Conversion
It is unwieldy to express any type larger than char in binary form, thus hex is an important friend to have. Mapping between base 16 and base 2 is straightforward and you should practice until it is second nature for you to see a hex value and visualize its binary equivalent and vice versa. (Converting between decimal and hex is less worthy of practice, and it is ok for it to be a chore in the isolated situations you have to convert manually.)
Hex | Binary |
---|---|
0x1 |
00000000 00000000 00000000 00000001 |
0x1ff |
00000000 00000000 00000001 11111111 |
0x800000 |
00000000 10000000 00000000 00000000 |
0xa017 |
00000000 00000000 10100000 00010111 |
Binary | Hex |
---|---|
11111111 11111111 11111111 11111111 |
0xffffffff |
00000010 00000000 10000000 00000000 |
0x2008000 |
00000000 00000000 00011111 11100000 |
0x1fe0 |
11111000 01111111 00000000 00000000 |
0xf87f0000 |
There is no need to memorize the entire ASCII table, but knowing the placement of a few key characters from which you can then find neighbors is worthwhile. Use man ascii
to learn the codes for the three characters '0' 'A' 'a'
and use them to do the following conversions.
char
.
Ascii | Hex |
---|---|
'I' |
0x49 |
'5' |
0x35 |
'd' |
0x64 |
What is the bitwise relationship between the placement of the lowercase and uppercase alphabet within the ASCII table?
Each lowercase letter is 32 + uppercase equivalent. This means simply flipping the bit at position 5 (counting from least significant bit at position 0) inverts the case of a letter.
You should also know the bit patterns for various distinctive unsigned and signed values.
Value | Binary |
---|---|
-1 |
11111111 11111111 11111111 11111111 |
INT_MAX |
01111111 11111111 11111111 11111111 |
INT_MIN |
10000000 00000000 00000000 00000000 |
UINT_MAX |
11111111 11111111 11111111 11111111 |
Unix Tools: gdb
Once you are comfortable with manual bit calculations, it's good to also learn how to use the Unix tools to do these calculations for you. The best tool to use for conversions is the debugger gdb. If you run it (even without a program to debug), you can use it as sort of a converter. The gdb print
command (shortened p
) defaults to decimal format. Use p/format
to instead select other formats such as x
for hex, t
for binary, and c
for char. Also check out the guide to GDB for more information about gdb.
$ gdb
(gdb) p 68
$1 = 68
(gdb) p/x 68
$2 = 44
(gdb) p/c 68
$3 = 'D'
(gdb) p/t 68
$4 = 1000100
(gdb) p 45 << 1 Note: can also print expressions, not just constants
$5 = 90
Another fun and handy tool to play with is this online visualizer for bitwise expressions written by Max Drach, a previous 107 CA. Check it out!
Bitwise Operators
You should know the behavior of all six bitwise operators in C: & | ~ ^ << >>
.
char
to keep things simple, but the bitwise operators have same behavior for int
, just more bits.)
Expression | Evaluates to |
---|---|
unsigned char this, that, result; |
|
this = 0xf0 |
11110000 |
that = 0x55 |
01010101 |
result = this & that |
01010000 = 0x50 |
result = this | that |
11110101 = 0xf5 |
result = this ^ that |
10100101 = 0xa5 |
result = ~this |
00001111 = 0x0f |
result = this >> 2 |
00111100 = 0x3c |
result = that << 1 |
10101010 = 0xaa |
Bitvectors
A bitvector is an unsigned value treated as a set of independent booleans. The bit at a given position represents a particular member and if that bit is on in the bitvector, the set contains that member. Changing a particular bit allows you to add or remove a member from the set. When operating on a bitvector, you apply a bitwise operator with a mask to isolate the bits of interest.
Bitwise operators are used to test, set, and clear individual bits and perform simple set operations. These are classic bitwise code idioms worth knowing!
mine
and yours
are unsigned ints. The terms "low" and "high" refer to "less significant" and "more significant", i.e. the lowest bit is the least significant. You will also see "rightmost" and "leftmost" used as synonyms for least and most significant.
How can you | C expression |
---|---|
test if mine has lowest bit on |
(mine & 1) != 0 |
set lowest bit in yours |
yours = yours | 1 |
clear lowest bit in yours |
yours = yours & ~1 |
toggle lowest bit in yours |
yours = yours ^ 1 |
union mine with yours |
mine = mine | yours |
intersect mine with yours |
mine = mine & yours |
remove yours from mine |
mine = mine & ~yours |
Other equivalent expressions are also valid answers. As an example, yours ^= 1
is equivalent to yours = yours ^ 1
.
Using and Constructing Bitmasks
When accessing an individual bit, a mask is used to isolate the bit of interest, e.g., 1 is the mask being used to test the least significant bit (mine & 1)
. Changing the mask changes which bit(s) is being operated on. For example, (mine & 2)
tests the second least significant bit. Including other bits into the mask allows you to affect an entire group of bits, e.g., mine ^= 0x7
to toggle the 3 lowest bits.
mine
is an unsigned int being used as bitvector. For each listed task, show how to express it in C.
How can you | C expression |
---|---|
test if mine has either of two lowest bits on |
(mine & 0x3) != 0 |
test if mine has both of two lowest bits on |
(mine & 0x3) == 0x3 |
set lowest 8 bits of mine |
mine |= 0xff |
clear every other bit in mine |
mine &= 0x55555555 or |
mine &= 0xaaaaaaaa |
0x55
as mask clears bits in position 1/3/5/etc. whereas using 0xaa
as mask clears bits in position 0/2/4/6, etc.
A constant mask should always be expressed in hex (few of us are good at visualizing binary from decimal, but hex to binary is a snap), the exception being small decimal integer constants (0, 1,...). For a complex mask, it is pleasing to express the mask by its construction, as opposed to hard-coding a magic number. You should practice with building up masks by construction. You should also define constants (#define <CONSTANT> <VALUE>
near the top of your file) where appropriate.
0
.
Construct mask that has | C expression |
---|---|
all bits on | ~0 or -1 |
one bit on in position n , all others off |
1 << n |
n least significant bits on, all others off |
(1 << n) - 1 |
most significant bit on, all others off | (1 << 31) |
k most significant bits on, all others off |
(~0 << (32 - k)) or |
~(~0U >> k) |
Other equivalent expressions are also valid answers. 'U' suffix on integer constant indicates unsigned.
Bitwise Manipulation
Lastly, we present some slightly fancier bitwise manipulations. These constructions can be tricky to reason about at first, but working through them confirms you have solid understanding of the bitwise operators and the relationship to the two's complement representation.
x
and y
are 32-bit signed ints and that right-shift performs an arithmetic (not logical) shift. For each listed expression, work out what result it produces.
Expression | What result does it produce? |
---|---|
1 << x |
2 to the power x |
~x + 1 |
-x , arithmetic negation |
x >> 31 |
-1 if x is negative, 0 otherwise |
x &= (x - 1) |
clears lowest "on" bit in x |
(x ^ y) < 0 |
true if x and y have opposite signs |
gdb
and observe these operations in action!
Questions about any of these? Post on the discussion forum or come by helper hours!
Source: cowbirdsinlove.com