Skip to content

Solutions: Lecture 3 In-Class Exercises

Instructor key for exercises.md. Keep separate from the student handout.


Part A - Number systems

Exercise A1 - The 0 to 15 table

Decimal Binary (4 bits) Hex
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F

Talking points: bit 0 toggles every row, bit 1 every 2 rows, bit 2 every 4, bit 3 every 8 - each bit turns over twice as slowly as the one to its right.

Exercise A2 - Bigger conversions

Decimal Binary (8 bits) Hex
42 0010 1010 0x2A
255 1111 1111 0xFF
128 1000 0000 0x80
200 1100 1000 0xC8
58 0011 1010 0x3A

Check the nibble-to-hex mapping: 0010 1010 -> 2 A, 1100 1000 -> C 8. Each group of 4 bits is read off the A1 table independently.

Exercise A3 - Binary addition

  1. 0010 1101 (45) + 0001 0011 (19) = 0100 0000 (64). Check: 45 + 19 = 64.
  2. 0100 0000 (64) + 0100 0000 (64) = 1000 0000 (128). Check: 64 + 64 = 128.
  3. 1111 1111 (255) + 0000 0001 (1). By hand this is 1 0000 0000 (256), but the result does not fit in 8 bits: the low 8 bits are 0000 0000 and the 1 is a carry out off the top. In a fixed-width unsigned byte this is overflow, and 255 + 1 wraps to 0.

Part B - Bits in C

Exercise B1 - Print a number in decimal, hex, and binary

#include <stdio.h>

void print_binary(unsigned int v) {
    for (int i = 31; i >= 0; i--) {
        printf("%c", (v >> i) & 1 ? '1' : '0');
        if (i % 8 == 0 && i != 0) {
            printf(" ");         /* space every 8 bits, for readability */
        }
    }
    printf("\n");
}

int main(void) {
    unsigned int v;
    printf("Enter a number: ");
    scanf("%u", &v);

    printf("decimal: %u\n", v);
    printf("hex:     0x%X\n", v);
    printf("binary:  ");
    print_binary(v);
    return 0;
}
Enter a number: 250
decimal: 250
hex:     0xFA
binary:  00000000 00000000 00000000 11111010
  • (v >> i) & 1 slides bit i down to position 0 and keeps only that bit.
  • Stretch (skip leading zeros): find the highest set bit first, then print from there down.
    int high = 31;
    while (high > 0 && !((v >> high) & 1)) {
        high--;
    }
    for (int i = high; i >= 0; i--) {
        printf("%c", (v >> i) & 1 ? '1' : '0');
    }
    

Exercise B2 - Set, clear, toggle, test

#include <stdio.h>

void print_binary(unsigned int v) {          /* reused from B1 */
    for (int i = 31; i >= 0; i--) {
        printf("%c", (v >> i) & 1 ? '1' : '0');
    }
    printf("\n");
}

int is_set(unsigned int x, int n) {
    return (x >> n) & 1;
}

int main(void) {
    unsigned int flags = 0;

    flags |= (1 << 1);            /* set bit 1 */
    flags |= (1 << 3);            /* set bit 3 */
    print_binary(flags);          /* ...0000 1010 */

    flags &= ~(1 << 1);           /* clear bit 1 */
    print_binary(flags);          /* ...0000 1000 */

    flags ^= (1 << 0);            /* toggle bit 0 on  */
    print_binary(flags);          /* ...0000 1001 */
    flags ^= (1 << 0);            /* toggle bit 0 off */
    print_binary(flags);          /* ...0000 1000 */

    printf("bits set (0-7): ");
    for (int n = 0; n <= 7; n++) {
        if (is_set(flags, n)) {
            printf("%d ", n);
        }
    }
    printf("\n");                  /* prints: 3 */
    return 0;
}

Why each idiom works:

  • |= (1 << n) - OR with a 1 forces bit n on; OR with 0 leaves the others untouched.
  • &= ~(1 << n) - ~(1 << n) is all 1s with a single 0 at position n; AND with 0 forces that bit off, AND with 1 leaves the rest.
  • ^= (1 << n) - XOR with 1 flips, XOR with 0 leaves unchanged - so only bit n flips.
  • (x >> n) & 1 - move bit n to the bottom, mask off everything else.

Stretch

1. Population count (set bits)

int popcount(unsigned int v) {
    int count = 0;
    for (int i = 0; i < 32; i++) {
        count += (v >> i) & 1;
    }
    return count;
}

2. XOR swap

a ^= b;     /* a now holds a^b              */
b ^= a;     /* b = b ^ (a^b) = a            */
a ^= b;     /* a = (a^b) ^ a = b            */

It works because XOR is its own inverse: x ^ y ^ y == x. (Caveat worth saying out loud: if a and b are the same variable, this zeroes it - so a temporary is usually the clearer choice in real code.)

3. Pack and unpack an RGB color

unsigned int color = (r << 16) | (g << 8) | b;

unsigned int red   = (color >> 16) & 0xFF;
unsigned int green = (color >> 8)  & 0xFF;
unsigned int blue  =  color        & 0xFF;

The & 0xFF keeps just the low 8 bits (one channel) after shifting it down. This is exactly how colors are stored in image formats - the practical payoff from Section 5 of the notes.