Many struggle with bitwise operators. I must admit I experienced difficulty at first; however, I soon realised that it wasn't the information that was incomprehensible, but more of the way it was presented. Consequently, I decided to create my own tutorial on the subject.
Our ancestors, who first walked this earth, communicated in only a basic language. Computers communicate in a similar fashion known variously as machine language. This language, also known as binary, consists of two digits: 0 and 1. It is possible, albeit irksome, to translate machine language into plausible information.
The following illustrates a basic composition of the binary language:
A single binary digit is known as a bit; collectively, bits.
A byte is comprised of 8 bits.
A short word, or word, is comprised of 16 bits (2 bytes).
A long word, or dword (double word), is comprised of 32 bits (4 bytes, 2 words).
Obviously, everything performed on a computer can be expressed in binary notation. Consider the simple arithmetic of 1 + 1. We visualise the two arguements (1 and 1) and their operator (+); a computer, however, does not. A computer must first convert this alien information into something it can comprehend. Only then can it proceed to perform the actual calculation. A computer's perception of this calculation could be better shown as:
0011 0001 + 0011 0001
In other words, 0011 0001 is the binary equivalent of the number 1.
The architecture of a computer itself is fundamental in how values are expressed. Most modern processors come in two flavours: 32 and 64 bit. The following displays an 8, 32 and 64 bit binary expression of the number 1.
Bit | Binary Equivalent |
---|---|
8 | 0011 0001 |
32 | 0000 0000 0000 0000 0000 0000 0011 0001 |
64 | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0011 0001 |
From the above, it is apparent that our 8 bit binary equivalent is still present in the 32 and 64 bit counterparts. The original 8 bit binary equivalent appears shifted from left to right. Refer to the Shift bitwise operators mentioned later in this document for a more detailed explanation.
Because binary consists of only two digits, obviously, expressing even a paltry amount of information would yield a lengthy result. Considering that the 8 bit binary equivalent of the number 1 is 0011 0001, imagine how verbose, for example, an entire sentence would be. Fortunately, there is a solution.
Top of PageHexadecimal could be described as a medium to binary and decimal notation. Hexadecimal utilises 16 characters when expressing a value: 0-F. Conveniently, 4 bits (half a byte) can be represented by one hexadecimal character. Thus, we may use two hexadecimal characters to represent one byte.
A group of 4 bits is known as a nibble. The following table illustrates all possible 4 bit binary combinations and their respective hexadecimal equivalents:
Nibble | Hexadecimal Equivalent |
---|---|
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
0111 | 7 |
1000 | 8 |
1001 | 9 |
1010 | A |
1011 | B |
1100 | C |
1101 | D |
1110 | E |
1111 | F |
Considering the above, it's now easier to convert a binary value to its hexadecimal equivalent and vice versa:
Number 1, in 8 bit binary, as stated, is 0011 0001 Now, matching each 4 bit portion (nibble) of this value from the table above, we note: 0011 == 3 0001 == 1 Thus, 0011 0001 == 31
Obviously, there must be a convention used for seperating a decimal value from its hexadecimal equivalent, otherwise we wouldn't be able to differentiate between the two. C++ adopts the convention of prefixing a hexadecimal value with a 0x. Thus, 31, in hexadecimal, would be expressed as 0x31. In shorthand, we say:
0011 0001 == 0x31
Augmenting further, consider the following:
Bitwise operators work on their arguements at the bit (binary) level; hence bitwise. Bitwise operations are considered to be low-level programming, meaning roughly that you probably won't encounter them too often; nevertheless, they are important. A good programmer is one who has a thorough understanding of all facets of the language in question.
There are six bitwise operators in C++:
Bitwise Operator | C++ Symbol |
---|---|
NOT | ~ |
AND | & |
OR | | |
XOR | ^ |
Left Shift | << |
Right Shift | >> |
As aforementioned, everything on a computer can be expressed in binary. Processing takes a certain amount of time, memory and patience. Even with today's fast processors, we should save resources where possible. Bitwise operations can, potentially, save a lot of strife.
There are many simple equations in our world; for example: you're either male or female. Similarly, there are many simple equations for a computer. A condition could be contracted to either true (1), or false (0). With this idea in mind, we may use bitwise operators to, potentially, store large amounts of data in small bit structures. Moreover, there's no penalty paid for using bitwise operators, they are extremely fast.
Top of PageThe not operator, also known as the ones' complement or inversion operator, is the easiest of all bitwise operators to understand. The NOT operator simply inverts a bit from 0 to 1 and vice versa; for example:
~ 0101 ------ 1010Top of Page
The AND operator compares two values and returns a value utilising the AND truth table. The AND truth table is as follows:
1 | & | 1 | == | 1 |
1 | & | 0 | == | 0 |
0 | & | 1 | == | 0 |
0 | & | 0 | == | 0 |
The methodology behind the AND operator is akin to simple arithmetic; consider:
123 + 456 ----- 579
In the calculation above, we work from top to bottom, left to right, and compare each of the two arguements against the operator (+) in order to calculate the subsequent 579. A bitwise AND calculation is as follows, and the corelation is quite obvious:
1100 & 1010 ------ 1000
As with our simple arithmetic, we begin by working from top to bottom, left to right, except in this instance we are comparing each of the two arguements against the AND truth table, as opposed to performing addition. Referring to the AND truth table, we note:
1100 & 1010 ------ 1000 In bit 1: 1 & 1 == 1 In bit 2: 1 & 0 == 0 In bit 3: 0 & 1 == 0 In bit 4: 0 & 0 == 0
Yielding 1000. In shorthand, we say:
1001 & 1100 == 1000Top of Page
The OR operator compares two values and returns a value utilising the OR truth table. The OR truth table is as follows:
1 | | | 1 | == | 1 |
1 | | | 0 | == | 1 |
0 | | | 1 | == | 1 |
0 | | | 0 | == | 0 |
When considering the AND and OR bitwise operators, the similarity to the short-circuit logical operators, && and ||, is apparent. Consider the following:
1 & 1 == 1 1 & 0 == 0 0 & 1 == 0 0 & 0 == 0 true && true == true true && false == false false && true == false false && false == false 1 | 1 == 1 1 | 0 == 1 0 | 1 == 1 0 | 0 == 0 true || true == true true || false == true false || true == true false || false == false
The boolean constants false and true are the integer equivalents of 0 and 1 respectively; thank you, George Boole.
Top of PageThe XOR operator is also known as the exclusive OR, or OR else operator. Considering that exclusive means incompatible, or limited; the XOR truth table becomes intelligible:
1 | ^ | 1 | == | 0 |
1 | ^ | 0 | == | 1 |
0 | ^ | 1 | == | 1 |
0 | ^ | 0 | == | 0 |
The Shift operators simply move our binary expressions of values the specified number of places to the left, or right. For example:
1001 << 2 ---- 0100
Any bit a Shift operator creates will be clear. To 0 a bit is also known as clearing. To 1 a bit is also known as setting.
Note that the Shift operators are not technically bitwise operators, as they don't operate on pairs of corresponding bits.
Top of PageWhat possible use could you have for bitwise operators? Well, you'd be surprised; for example:
Perhaps we wish to differentiate between odd and even numbers. The bitwise AND operator is an excellent tool in this instance. Let us first examine the 8 bit binary expressions of numbers, say, from 0 to 5:
Number | 8 Bit Binary Equivalent |
---|---|
0 | 0011 0000 |
1 | 0011 0001 |
2 | 0011 0010 |
3 | 0011 0011 |
4 | 0011 0100 |
5 | 0011 0101 |
As we note from the above, the highlighted bit in each expression clearly identifies a pattern. Each bit is either set, or cleared, in each instance. Thus, we may use the AND operator for validation purposes. Consider the following C++ code:
for (int i = 0; i <= 5; i++) { if (i & 0x01) cout << i << " is an odd number" << endl; else cout << i << " is an even number" << endl; }
We employ the hexadecimal equivalent of 8 bit binary 0000 0001, 0x01, to validate whether the corresponding bit is either set, or clear. This method of segregating individual bits for clarity is also known as bit masking.
Top of PageI do hope I have achieved something in this document. There is a whole myriad of information available on the Internet regarding this subject. Where you journey from here is, of course, entirely up to you.