CS253: Software Development with C++

Fall 2021

Bit Manipulation

Show Lecture.BitManipulation as a slide show.

CS253 Bit Manipulation

Kat Dennings as Max Black

Bases

Writing numbers (mathematics)

Writing numbers (C++ source code)

cerr << 0b11 << ' ' << 0B101 << ' ' << 0x1a << ' ' << 0XfF << ' ' << 012 << ' ' << 34;
3 5 26 255 10 34

Place notation

Consider an ordinary, three-digit base 10 number. It has a hundreds place, a tens place, and a ones place. You know this so well that you’ve forgotten it.

So, 78910 means:

Place notation

Now, consider a base 8 number, such as 3758. This means:

Place notation

A binary number, such as 101102, means:

Place notation

Of course, in practice, we wouldn’t bother writing down the terms with zero coefficients, since they contribute only zeroes to the sum.

A binary number, such as 101102, means:

I’m thinking of a number…

0644 in octal… okayyyyyy…

What is 0644 in binary?

Hardest way is convert to decimal, then convert to binary.

What is 0644 in octal?

What is 0644 in decimal?

What is 0644 in hexadecimal?

What is 0644 in hexadecimal?

Why doesn’t the easy way always work?

It’s all about the ratios:

FromToRatioDifficulty
HexadecimalBinary16 = 24Easy
HexadecimalOctal16 = 84/3Not so bad
HexadecimalDecimal16 = 101.20412Hard
DecimalOctal10 = 81.1073Hard
DecimalBinary10 = 23.3219Hard
OctalBinary8 = 23Easy

Everything’s simple, unless stupid decimal is involved.

So, our number is:

1101001002 or 6448 or 42010 or 1a416

The computer doesn’t care!

cout << 0b10000 << ' ' << 020 << ' ' << 16 << ' ' << 0x10 << '\n';
16 16 16 16

How does it know?

It doesn’t know—you have to tell it!

int x = 060 - 0x00000000000000000000006;
cout << x << '\n'
     << oct << x << '\n'
     << hex << x << '\n'
     << dec << x << '\n';
42
52
2a
42

Refresher

C++ bit-manipulation tools:

Precedence matters

cout << (1|2) << '\n';
3

However, the binary bitwise operators are low precedence:

cout << 1|2 << '\n';
c.cc:1: error: no match for 'operator|' in '
   std::cout.std::basic_ostream<char>::operator<<(1) | (2 << 10)' (operand 
   types are 'std::basic_ostream<char>' and 'int')

It’s trying to evaluate (cout << 1) | (2 << '\n');

Setting a bit

int n = 32;
cout << hex << n << '\n'
     << hex << (n|02) << '\n';
20
22

Or, using assignment and binary literals:

int n = 32;
cout << hex << n << '\n';
n |= 0b10;
cout << hex << n << '\n';
20
22

Clearing bits

cout << hex << 126 << '\n'
     << hex << (126 & ~0xF) << '\n';
7e
70

Testing bits

for (char c='A'; c<'N'; c++)
    cout << ((c & 0x01) ? "Odd:  " : "Even: ")
         << "'" << c << "' (" << int(c) << ")\n";
Odd:  'A' (65)
Even: 'B' (66)
Odd:  'C' (67)
Even: 'D' (68)
Odd:  'E' (69)
Even: 'F' (70)
Odd:  'G' (71)
Even: 'H' (72)
Odd:  'I' (73)
Even: 'J' (74)
Odd:  'K' (75)
Even: 'L' (76)
Odd:  'M' (77)

Shifting

int n = 42;
cout << oct << n << '\n';
n >>= 1;
cout << n << '\n';
n >>= 2;
cout << n << '\n';
n >>= 1;
cout << n << '\n';
n >>= 1;
cout << n << '\n';
n >>= 1;
cout << n << '\n';
52
25
5
2
1
0

Puzzle

int a = 1<<4;
cout << a << '\n';
cout << 1<<4 << '\n';
16
14
Why does using a variable give a different result?

Parsing! << is left-associative, so cout << 1<<4 parses as (cout << 1) << 4 and not as cout << (1 << 4).

Badness

The shift amount must be ≥ 0 and < word size in bits. That’s 0…31 for a 32-bit integer. Shifting a negative number of positions is not allowed.

static int con67 = 67, conm1 = -1;
cout << (1<<con67) << '\n';
cout << (0b1000 << conm1) << '\n';
8
0

Using static fooled the optimizer.

Badness

Java has two different right-shift operators:

System.out.println(42 >> 1);
System.out.println(-1 >> 1);
System.out.println(42 >>> 1);
System.out.println(-1 >>> 1);
21
-1
21
2147483647

In C++, when shifting negative values right, it is implementation-defined whether or not the sign bit is propagated:

cout << (-1 >> 1) << '\n';
-1

char vs. byte