Return to lecture notes index
November 6, 2008 (Lecture 18)

C's Bit-Wise Operators

C has several operators that are defined in terms of manipulations upon the individual bits that compose values, rather than upon the whole values. These operators include the following:

  << -- left shift
  >> -- right shift
  |  -- bit-wise OR
  &  -- bit-wise AND
  ~  -- bit-wise negation
  ^  -- bit-wise XOR
  

Left Shift

The effect of the left shift operator is pretty easy to understand. At a high-level, it acts to multiply numbers by powers of two. For example:

  int x = 5;
  int y;

  y  = x << 1; /* Multiply x by 2^1 */
  printf ("%d\n", y); /* 10 */

  y  = x << 3; /* Multiply x by 2^3 */
  printf ("%d\n", y); /* 40 */
  

The reason that this works is that numbers are stored in memory in what is, in effect, a binary format. In decimal, each position represents a power of 10: 100=10^2, 10=10^1, 1=10^0. Each binary digit (bit) represents a power of 2.

So, each time we shift by one posititon to the left, back-filling the vacated positions with 0s, we are, making each of the shifted bits worth twice as much:

  5  =    0101
  10 =    1010  (after shifting left by one: 5 << 1)
  20 =   10100  (after shifting left by two: 5 << 2)
  

Right Shift

In theory a right-shift is the opposite of a left shift. It has the effect of dividing by two, as shown below:

  5  =    0101
  2  =    0010  (after shifting right by one: 5 >> 1)
  1  =    0001  (after shifting right by two: 5 >> 2)
  

And, what we've got above is exactly true for unsigned types and also for any non-negative value. But, the Right Thing to do gets a bit confused when it comes to signed types. In my experience, the effect will always be the same as dividing by two. But, this is absolutely not guaranteed by the standard -- the standard leaves the effect of any right-shift of a negative value completely up to the compiler.

The reason for this you'll study, in detail, in 15-213. It has to do with the fact that most hardware does not represent negative numbers in the same way as it does positive numbers. It uses an encoding known as two's compliment. This encoding makes, when a negative number is involved, simple math much easier to implement in hardware.

But, it also means that a so-called "arithmetic right shift" is harder to do. Instead of always shifting a 0 in on the right side, a 1 must be shifted in if the exisitng left-mst bit is a 1.

Although I think the standard should have been for the right shift \ to be "arithmetic", meaning have the same effect as dividing by a power of two, the standard leaves this to the compiler.

The Bit-Wise AND and OR Operators

The bit-wise AND and bit-wise OR operators act exactly as do their so-called logical version, but they operate at a per-bit, bit-wise level upon corresponding bits of the two operands, as shown below:

  /* 
   * Notice that only those bits that are 1 in both of the corresponding
   * input bits are 1 in the result 
   */
  x  = 9;         /* 1001 */
  y  = 5;         /* 0101 */
  z = x & y;      /* 0001 */

  /* 
   * Notice that those bits that are 1 in EITHER (or both) of the corresponding
   * input bits are 1 in the result
   */
  x  = 9;         /* 1001 */
  y  = 5;         /* 0101 */
  z = x | y;      /* 1101 */
  

Bit-wise Exclusive OR and Bit-wise Negation

Although there is no logical XOR in C, there is a bit-wise version. It does exactly as one might expect. Each result bit is true if, and only if, exactly one of the corresponding input bits is on. A result bit is false in the case that both correspnding bits are in the same state, whether true or false, as shown in the example below:

  /* 
   * Notice that, in order for a result bit to be one, EXACTLY one of the 
   * corresponding input bits must be one. 
   */
  x  = 9;         /* 1001 */
  y  = 5;         /* 0101 */
  z = x ^ y;      /* 1100 */
  

The negation operator simply flips each bit. Each one becomes a zero, and vice-versa:

  /* 
   * Notice that, in order for a result bit to be one, EXACTLY one of the 
   * corresponding input bits must be one. 
   */
  x  = 9;         /* 1001 */
  y = ~x;         /* 0110 */