The Bitwise Operators


C# provides a set of bitwise operators that expand the types of problems to which C# can be applied. The bitwise operators act directly upon the bits of their operands. They are defined only for integer operands. They cannot be used on bool, float, or double.

They are called the bitwise operators because they are used to test, set/or shift the bits that comprise an integer value. Bitwise operations are important to a wide variety of systems-level programming tasks, such as when status information from a device must be interrogated or constructed. Table 4-1 lists the bitwise operators.

Table 4-1: The Bitwise Operators

Operator

Result

&

Bitwise AND

|

Bitwise OR

^

Bitwise exclusive OR (XOR)

»

Shift right

«

Shift left

~

One’s complement (unary NOT)

The Bitwise AND, OR, XOR, and NOT Operators

The bitwise operators AND, OR, XOR, and NOT are &, |, ^, and ~, respectively. They perform the same operations as their Boolean logic equivalents described earlier. The difference is that the bitwise operators work on a bit-by-bit basis. The following table shows the outcome of each operation using 1’s and 0’s:

p

q

p & q

p | q

p ^ q

~p

0

0

0

0

0

1

1

0

0

1

1

0

0

1

0

1

1

1

1

1

1

1

0

0

In terms of its most common usage, you can think of the bitwise AND as a way to turn bits off. That is, any bit that is 0 in either operand will cause the corresponding bit in the outcome to be set to 0. For example,

   1 1 0 1 0 0 1 1 & 1 0 1 0 1 0 1 0   ----------------   1 0 0 0 0 0 1 0 

The following program demonstrates the & by using it to convert odd numbers into even numbers. It does this by turning off bit zero. For example, the number 9 in binary is 0000 1001. When bit zero is turned off, this number becomes 8, or 0000 1000 in binary.

 // Use bitwise AND to make a number even, using System; class MakeEven {   public static void Main() {     ushort num;     ushort i;     for(i = 1; i <= 10; i++) {       num = i;       Console.WriteLine("num: " + num);       num = (ushort) (num & 0xFFFE); // num & 1111 1110       Console.WriteLine("num after turning off bit zero: "                         + num + "\n");     }   } }

The output from this program is shown here:

 num: 1 num after turning off bit zero: 0 num: 2 num after turning off bit zero: 2 num: 3 num after turning off bit zero: 2 num: 4 num after turning off bit zero: 4 num: 5 num after turning off bit zero: 4 num: 6 num after turning off bit zero: 6 num: 7 num after turning off bit zero: 6 num: 8 num after turning off bit zero: 8 num: 9 num after turning off bit zero: 8 num: 10 num after turning off bit zero: 10

The value 0xFFFE used in the AND statement is the decimal representation of 11111110. Thus, the AND operation leaves all bits in num unchanged except for bit zero, which is set to zero. Therefore, any even number is unchanged, but odd numbers are made even by reducing their values by 1.

The AND operator is also useful when you want to determine whether a bit is on or off. For example, this program determines if a number is odd:

 // Use bitwise AND to determine if a number is odd. using System; class IsOdd {   public static void Main() {     ushort num;     num = 10;     if((num &1)==1)       Console.WriteLine("This won't display.");     num = 11;     if((num & 1) == 1)       Console.WriteLine(num + " is odd.");   } }

The output is shown here:

 11 is odd.

In the if statements, the value of num is ANDed with 1. If bit zero in num is set, the result of num & 1 is 1; otherwise, the result is zero. Therefore, the if statement can succeed only when the number is odd.

You can use the bit-testing capability of the bitwise & to create a program that uses the bitwise & to show the bits of a byte value in binary format. Here is one approach:

 // Display the bits within a byte, using System; class ShowBits {   public static void Main() {     int t;     byte val;     val = 123;     for(t=128; t > 0; t = t/2) {       if((val & t) != 0) Console.Write("1 ");       if((val & t) == 0) Console.Write("0 ");     }   } } 

The output is shown here:

 0 1 1 1 1 0 1 1

The for loop successively tests each bit in val, using the bitwise AND to determine if it is on or off. If the bit is on, the digit 1 is displayed; otherwise, 0 is displayed.

The bitwise OR, as the reverse of AND, can be used to turn bits on. Any bit that is set to 1 in either operand will cause the corresponding bit in the variable to be set to 1. For example,

   1 1 0 1 0 0 1 1 | 1 0 1 0 1 0 1 0   -----------------   1 1 1 1 1 0 1 1

You can make use of the OR to change the make-even program shown earlier into a make-odd program, as shown here:

 // Use bitwise OR to make a number odd. using System; class MakeOdd {   public static void Main() {     ushort num;     ushort i;     for(i = 1; i <= 10; i++) {       num = i;       Console.WriteLine("num: " + num);       num = (ushort) (num | 1); // num | 0000 0001       Console.WriteLine("num after turning on bit zero: "                         + num + "\n");     }   } }

The output from this program is shown here:

 num: 1 num after turning on bit zero: 1 num: 2 num after turning on bit zero: 3 num: 3 num after turning on bit zero: 3 num: 4 num after turning on bit zero: 5 num: 5 num after turning on bit zero: 5 num: 6 num after turning on bit zero: 7 num: 7 num after turning on bit zero: 7 num: 8 num after turning on bit zero: 9 num: 9 num after turning on bit zero: 9 num: 10 num after turning on bit zero: 11 

The program works by ORing each number with the value 1, which is 0000 0001 in binary. Thus, 1 is the value that produces a value in binary in which only bit zero is set. When this value is ORed with any other value, it produces a result in which the low-order bit is set and all other bits remain unchanged. Thus, a value that is even will be increased by one, becoming odd.

An exclusive OR, usually abbreviated XOR, will set a bit on if, and only if, the bits being compared are different, as illustrated here:

   0 1 1 1 1 1 1 1 ^ 1 0 1 1 1 0 0 1   ------------------   1 1 0 0 0 1 1 0

The XOR operator has an interesting property that makes it a simple way to encode a message. When some value X is XORed with another value Y, and then that result is XORed with Y again, X is produced. That is, given the sequence

 R1 = X ^ Y; R2 = R1 ^ Y;

R2 is the same value as X. Thus, the outcome of a sequence of two XORs using the same value produces the original value. You can use this principle to create a simple cipher program in which some integer is the key that is used to both encode and decode a message by XORing the characters in that message. To encode, the XOR operation is applied the first time, yielding the ciphertext. To decode, the XOR is applied a second time, yielding the plaintext. Here is a simple example that uses this approach to encode and decode a short message:

 // Use XOR to encode and decode a message, using System; class Encode {   public static void Main() {     char ch1 = 'H';     char ch2 = 'i';     char ch3 = '!';     int key = 88;     Console.WriteLine("Original message: " + ch1 + ch2 + ch3);     // encode the message     ch1 = (char) (ch1 ^ key);     ch2 = (char) (ch2 ^ key);     ch3 = (char) (ch3 ^ key);     Console.WriteLine("Encoded message: " + ch1 + ch2 + ch3);     // decode the message     ch1 = (char) (ch1 ^ key);     ch2 = (char) (ch2 ^ key);     ch3 = (char) (ch3 ^ key);     Console.WriteLine("Encoded message: " + ch1 + ch2 + ch3);   } }

Here is the output:

 Original message: Hi! Encoded message: ly Encoded message: Hi!

As you can see, the result of two XORs using the same key produces the decoded message.

The unary one's complement (NOT) operator reverses the state of all the bits of the operand. For example, if some integer called A has the bit pattern 1001 0110, then ~A produces a result with the bit pattern 0110 1001.

The following program demonstrates the NOT operator by displaying a number and its complement in binary:

 // Demonstrate the bitwise NOT. using System; class NotDemo {   public static void Main() {     sbyte b = 34;          for(int t=128; t > 0; t = t/2) {       if((b & t) != 0) Console.Write("1       if((b & t) == 0) Console.Write("0     }     Console.WriteLine();          // reverse all bits     b = (sbyte) ~b;          for(int t=128; t > 0; t = t/2) {       if((b & t) != 0) Console.Write("1       if((b & t) == 0) Console.Write("0     }   } }

Here is the output:

 1 1 0 1 1 1 1 0 0 0 1 0 0 0 0 1

The Shift Operators

In C# it is possible to shift the bits that comprise an integer value to the left or to the right by a specified amount. C# defines the two bit-shift operators shown here:

«

Left shift

»

Right shift

The general forms for these operators are shown here:

 value « num-bits value » num-bits 

Here, value is the value being shifted by the number of bit positions specified by num-bits.

A left shift causes all bits within the specified value to be shifted left one position and a zero bit to be brought in on the right. A right shift causes all bits to be shifted right one position. In the case of a right shift on an unsigned value, a zero is brought in on the left. In the case of a right shift on a signed value, the sign bit is preserved. Recall that negative numbers are represented by setting the high-order bit of an integer value to 1. Thus, if the value being shifted is negative, each right shift brings in a 1 on the left. If the value is positive, each right shift brings in a 0 on the left.

For both left and right shifts, the bits shifted out are lost. Thus, a shift is not a rotate, and there is no way to retrieve a bit that has been shifted out.

Here is a program that graphically illustrates the effect of a left and right shift. An integer is given an initial value of 1, which means that its low-order bit is set. Then, eight shifts are performed on the integer. After each shift, the lower eight bits of the value are shown. The process is then repeated, except that a 1 is put in the eighth bit position, and right shifts are performed.

 // Demonstrate the shift « and » operators, using System; class ShiftDemo {   public static void Main() {     int val =1;          for(int i = 0; i < 8; i++) {       for(int t=128; t > 0; t = t/2) {         if((val & t) != 0) Console.Write("1 ");         if((val & t) == 0) Console.Write("0 ");       }       Console.WriteLine();       val = val « 1; // left shift     }     Console.WriteLine ();          val = 128;     for(int i = 0; i < 8; i++) {       for(int t=128; t > 0; t = t/2) {         if((val & t) != 0) Console.Write("1 ");         if((val & t) == 0) Console.Write("0 ");       }       Console.WriteLine();       val = val » 1; // right shift     }   } }

The output from the program is shown here:

 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1  

Since binary is based on powers of 2, the shift operators can be used as a very fast way to multiply or divide an integer by 2. A shift left doubles a value. A shift right halves it. Of course, this works only as long as you are not shifting bits off one end or the other. Here is an example:

 // Use the shift operators to multiply and divide by 2. using System; class MultDiv {   public static void Main() {     int n;     n = 10;     Console.WriteLine("Value of n: " + n);     // multiply by 2     n = n « 1;     Console.WriteLine("Value of n after n = n * 2: " + n);     // multiply by 4     n = n « 2;     Console.WriteLine("Value of n after n = n * 4: " + n) ;     // divide by 2     n = n » 1;     Console.WriteLine("Value of n after n = n / 2: " + n);     // divide by 4     n = n » 2;     Console.WriteLine("Value of n after n = n / 4: " + n);     Console.WriteLine();     // reset n     n = 10;     Console.WriteLine("Value of n: " + n);     // multiply by 2, 30 times     n = n « 30; // data is lost     Console.WriteLine("Value of n after left-shifting 30 places: " + n);   } }

The output is shown here:

 Value of n: 10 Value of n after n = n * 2: 20 Value of n after n = n * 4: 80 Value of n after n = n / 2: 40 Value of n after n = n / 4: 10 Value of n: 10 Value of n after left-shifting 30 places: -2147483648  

Notice the last line in the output. When the value 10 is left-shifted 30 times (that is, when it is multiplied by 230), information is lost because bits are shifted out of the range of an int. In this case, the garbage value produced is negative because a 1 bit is shifted into the high-order bit, which is used as a sign bit, causing the number to be interpreted as negative. This illustrates why you must be careful when using the shift operators to multiply or divide a value by 2. (See Chapter 3 for an explanation of signed vs. unsigned data types.)

Bitwise Compound Assignments

All of the binary bitwise operators can be used in compound assignments. For example, the following two statements both assign to x the outcome of an XOR of x with the value 127:

 x = x ^ 127; x ^= 127;




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net