C++: Bitwise Logical Operators

Bitwise Logical Operators

In this C++ video tutorial, we introduce the bitwise logical operators. The bitwise logical operators are similar to the logical operators from a previous lesson, except that they operate on bits instead of boolean logical values. Like the logical operators, the bitwise operators have an "or," "and," and "not." However, the bitwise operators also have an "exclusive or." In this lesson, we give example applications from bioinformatics and computer graphics, using DNA sequences and images of Christ, the cross, and a crucifix.

While the logical operators act on the logical bool values true and false, the bitwise logical operators act on the corresponding bit values 1 and 0. In this manner, we can construct "truth" tables just as we did for the logical operators, substituting in the values 0 and 1 in place of false and true.

Recall that the tokens for designating the logical operators are &&, ||, and ! for and, or, and not. For the bitwise logical operators, they are designated by &, |, and ~, respectively. Additionally, the bitwise logical operators contain an exclusive or operator which is designated by ^.

As an application of the bitwise exclusive or to bioinformatics, we have a simple program that demonstrates how to encode the bases of a DNA sequence. Since there are four bases Adenine, Cytosine, Guanine, and Thymine, we can encode each base with two bits. Using the single-letter abbreviations for the deoxyribonucleic acid (DNA) bases, we set A = 00, C = 01, G = 10, and T = 11.

DNA with Base Encoding Scheme

It is well-known that A always bonds with T on the opposite DNA strand and C always bonds with G to form the two bonded base pairs. With the given encoding, the bonded bases form bitwise complements. This means that we can get from the bases of one strand to the bases of the opposite strand by applying the exclusive or operator to the sequence of all ones. This is demonstrated in the program below.

#include <iostream>

void PrintBits(unsigned int uiBits) {
	using namespace std;
	for (int i = 31; i >= 0; --i) {
		cout << ((uiBits >> i) & 1);
		if (i %2 == 0) {
			cout << " ";
		}
	}
	cout << endl;
}

int main() {
	using namespace std;

	unsigned int uiDNA = 0x8C9BF570;
	unsigned int uiComp = uiDNA ^ 0xFFFFFFFF;
	cout << "DNA  :";
	PrintBits(uiDNA);
	cout << "Comp :";
	PrintBits(uiComp);

	return 0;
}

The program above encodes 16 bases in the 32-bit unsigned int uiDNA. The function PrintBits() has been modified from a previous version to insert a space between each two bits. This segments the bits into 2-bit base encodings. The opposite strand, uiComp is generated via the bitwise exclusive-or operator. Executing the program generates the following output:

DNA Sequence Program Output

Another application of bitwise logical operators comes from the area of black and white graphics that were common in the early days of computing. With bitwise logical operators, we can use or to combine graphics, and to perform intersection detection, xor (exclusive-or) to create animation, and not to create a reverse image.

Crucifix, Christ, and Cross

The program below prints out "graphics" as a series of 0s and 1s. On the screen or in an image these 0s and 1s correspond to black and white pixels. The PrintAll() function below is used to print each 24-line graphic. The 24-line graphics are specified by the following unsigned int arrays: uiaHoriz, uiaVert, uiaCross, and uiaChrist. The first two arrays hold "images" of a horizontal line and a vertical line. The third is initiallly blank, but is filled with the image of a cross via the or operator. The fourth array is initialized with an image of Christ, which is put on the cross via an xor to create a crucifix.

#include <iostream>

void PrintBits(unsigned int uiBits) {
	using namespace std;
	for (int i = 31; i >= 0; --i) {
		cout << (((uiBits >> i) % 2) ? '1' : '0');
	}
	cout << endl;
}

void PrintAll(unsigned int uiaBitArray[], int iArraySize) {
	using namespace std;
	for (int i = 0; i < iArraySize; ++i) {
		PrintBits(uiaBitArray[i]);
	}
	cout << endl;
}

int main() {
	using namespace std;

	unsigned int uiaHoriz[] = {
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x3FFFFFFC, 0x3FFFFFFC,
		0x3FFFFFFC, 0x3FFFFFFC, 0x3FFFFFFC,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0 };

	unsigned int uiaVert[] = {
		0x0, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x000FF000,
		0x000FF000, 0x000FF000, 0x0 };

	unsigned int uiaCross[] = {
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x0 };

	unsigned int uiaChrist[] = {
		0x0, 0x0, 0x0,
		0x0, 0x0, 0x00018000,
		0x0003C000, 0x0003C000, 0x0E018070,
		0x07FFFFE0, 0x00FFFF00, 0x0007E000,
		0x0003C000, 0x0003C000, 0x0003C000,
		0x0003C000, 0x0003C000, 0x00018000,
		0x00018000, 0x00018000, 0x00018000,
		0x0003C000, 0x0, 0x0 };

	int iArraySize = 24;

	PrintAll(uiaHoriz, iArraySize);
	PrintAll(uiaVert, iArraySize);
	PrintAll(uiaChrist, iArraySize);

	// OR
	for (int i = 0; i < iArraySize; ++i) {
		uiaCross[i] = uiaHoriz[i] | uiaVert[i];
	}
	PrintAll(uiaCross, iArraySize);

	// XOR
	for (int i = 0; i < iArraySize; ++i) {
		uiaCross[i] = uiaCross[i] ^ uiaChrist[i];
	}
	PrintAll(uiaCross, iArraySize);

	return 0;
}

Executing the program, we see the horizontal bar, the vertical bar, Christ, the cross, and the crucifix. However, you will need to scroll up to see them all.

DNA Sequence Program Output