Bitwise Operators: Combining Efficiency and Ease of Use

Bitwise Operators: Combining Efficiency and Ease of Use

irect manipulation of bits that aren’t stored as bit-fields is an arduous and error-prone task. Fortunately, C++ has a set of bitwise operators for this purpose. These operators are widely used in Digital Signal Processing, compression and encryption algorithms, GUI, and other application domains. The following sections introduce the bitwise operators and show how to use them.

How to manipulate strings of bits directly, in an efficient and readable manner?

Use the built-in bitwise operators for setting, clearing, and testing bit strings.

Operators’ Functionality
Unlike the logical and relational operators !, &&, ||, >, ==, etc., which yield a Boolean value, bitwise operators yield a sequence of bits in the form of an integral datatype such as char, short, int, and so on. The bitwise operators and their names are:

  • ~ bitwise NOT, or one’s complement (unary)
  • & bitwise AND
  • | bitwise OR
  • ^ bitwise XOR (exclusive OR)

Here’s a brief description of each operator and its functionality.The ~ operator flips, or reverses, every bit in a sequence:

typedef unsigned char BYTE;BYTE s=127; //binary: 0111 1111BYTE flipped_s= ~s; //dec 128, binary 1000 0000
Author’s Note: Bit and byte ordering are platform-dependent. Certain processors read bits from right-to-left. On such machines, the rightmost bit is also the least significant bit (LSB). On other processors the LSB is the leftmost bit. All the examples given here use an order that is similar to the way humans read numbers such as 345, whereby the least significant digit is the rightmost one. To improve readability, I inserted a blank between every four bits and have confined my examples to unsigned char. In real code, you may of course use any integral type.

The operator & ands every bit in a sequence with a corresponding bit of another sequence. For example, applying & to the following bit strings

0011 1111 //decimal value: 630100 0011 //decimal value 67: produces 0000 0011:BYTE s1 = 63;   // hex 0x3f, binary 0011 1111BYTE s2 = 67;       // 0x43,        0100 0011 BYTE res = s1 & s2; // 0x03,        0000 0011

The | operator ors every bit in a sequence with a corresponding bit of another sequence. For example:

BYTE s1 = 63;       // 0x3f, 0011 1111BYTE s2 = 67;       // 0x43, 0100 0011 BYTE res = s1 | s2; // 0x7f, 0111 1111

The ^ operator xors every bit in a sequence with a corresponding bit of another sequence. For every such pair, the result is 1 only if one of the two bits equals 1. Otherwise, the result is 0:

BYTE s1=63;        // 0x3f, 0011 1111BYTE s2=67;        // 0x43, 0100 0011 BYTE res= s1 ^ s2; // 0x7c, 0111 1100

In addition to the basic four operators shown, C++ defines three self-assigning versions thereof:

  • &= self-assigning bitwise AND
  • |= self-assigning bitwise OR
  • ^= self-assigning bitwise XOR

Thus, the previous code listing can be rewritten like this:

BYTE s1=63; // 0x3f, 0011 1111BYTE s2=67; // 0x43, 0100 0011 s1^=s2; // equivalent to: s1 = s1 ^ s2 

Two additional operators are left shift and right shift:

  • << left shift
  • >> right shift

>> shifts bits by n positions to the right. For example, if you take the string:

0011 1111 //decimal value: 63

Shifting it by one position to the right produces:

0001 1111 //decimal value: 31BYTE s1 = 63; //0x3f, 0011 1111BYTE result =     s1 >> 1; // 0x1f, 0001 1111

A graphic illustration of this operation may demystify the underlying steps. First, you have an eight bit string whose boundaries are marked with a pair of bars:

|0011 1111|

The right shift operation causes the leftmost bit’s position to become empty (indicated by an underscore). The rightmost bit overflows outside the string’s boundaries:

|_0011 111|1

In the second step, the processor pads empty positions with zeros and discards bits that were pushed outside the string’s boundaries (this rule applies only to unsigned types; the effect of shifting signed values is machine-dependent):

|00011 111|_

Of course, these steps don’t necessarily correspond to the underlying machine instructions that your processor employs but the effect is the same. Bit shifting happens to be a very efficient operation. This is why time-critical applications often use shift operators as a faster alternative to the division and multiplication operators. In the above example, the shift operation has the same effect as dividing the original value by two. In a similar vein, you can get the effect of multiplying a number by two if you apply the left shift operator. For example, left shifting the number 63 by one position produces 126:

BYTE s1 = 63;          // 0x3f,      0011 1111BYTE res = s1 << 1; //dec 126, 0x73, 0111 1110

Using the self-assignment versions of these operators, the previous code can be rewritten like this:

BYTE s1 = 63; //0x3f, 0011 1111// the expression 's1 >> 2;' divides s1 by 4  s1 >>= 2; // 15, 0x0f, 0000 1111

Wise Up
Bitwise operators are useful in bit masking, i.e., setting or examining bits in a bit string. Take for example a simple word processing application. The text in a document (or a portion thereof) may have the following features: bold, italics, underlined, or none. Each of these features can be represented as an individual bit:

const BYTE BOLD   = 0x01; // first bit const BYTE ITAL   = 0x02; // second bit const BYTE UNDERL = 0x04; // third bit 

Users may combine some of these features, all of them or none:

BYTE style = BOLD | ITAL;//turn on bold and italics

The following code tests whether underline and bold are on using bit masks:

bool isul=style & UNDERL; // falsebool isbold=style & BOLD; // true

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist