• Nem Talált Eredményt

Bit operations

3. Basic operations and expressions

3.7. Bit operations

Computers had quite small memory in the past therefore solutions that made it possible to store and process more data within one byte, the smallest unit that can be addressed, were quite worthy. Using bit operations even 8 logical values can be stored within one byte. Nowadays this aspect is only considered rarely, easily understandable programs are in focus instead.

However, there is a field where bit operations are still used, and that is programming different hardware elements, microcontrollers. The C++ language contains six operators with the help of which different bitwise operations can be carried out on signed and unsigned integer data.

3.7.1. Bitwise logical operations

The first group of operations, the bitwise logical operations make it possible to test, delete or set bits:

Operator Operation

~ Unary complement, bitwise negation

& bitwise AND

| bitwise OR

^ bitwise exclusive OR

The description of bitwise logical operations can be found in the table below, where 0 and 1 numerals denote deleted and set bit status, respectively.

a b a & b a | b a ^ b ~a

0 0 0 0 0 1

0 1 0 1 1 1

1 0 0 1 1 0

1 1 1 1 0 0

The low level control of the computer hardware elements requires setting, deletion and switching of certain bits.

All these operations are called “masking” since an adequate bitmask should be prepared for every single operation, and then should also be linked logically with the value desired to be changed, and this way the desired bit operation takes place.

Before all conventional bit operations are described one after the other, bit numbering in integer data elements has to be discussed. Bit numbering in a byte starts from the smallest order bit from 0 and increases from right to left. In case of integers composed of more bytes the byte sequence applied by the processor of the computer should also be discussed.

In case “big-endian” byte sequence supported by Motorola 68000, SPARC, PowerPC etc. processors the most significant byte (MSB) is stored in the lowest memory address, while an order smaller byte in the next address and so on.

However, the members of the most widespread Intel x86 based processor family use “little-endian” byte sequence according to which the least significant byte (LSB) is stored in the lowest memory address.

In order to avoid long bit series unsigned short int type data elements are used in our examples. Let‟s take a look at the structure and storing of these data according to both byte sequences. The stored data is 2012 that is 0x07DC in hexadecimal numbering.

big-endian byte sequence:

little-endian byte sequence:

(Memory addresses increase from left to right in the example.) The figure shows clearly that when the hexadecimal constant values are entered the first big-endian form is used that corresponds to the mathematical interpretation of the hexadecimal number system. This is not an issue since storing in the memory is the task of the compiler. However, if the integer variables are processed bytewise, byte sequence should be known.

Hereinafter our example programs are prepared in little-endian form but they can be adapted to the first storage method, as well based on the above mentioned facts.

In the example below bits 4 and 13 of the unsigned shortint type number 2525 are handled: unsigned short int x = 2525; // 0x09dd

Operation Mask C++ instruction Result

Bit setting 0010 0000 0001 0000 x = x | 0x2010; 0x29dd

Bit deletion 1101 1111 1110 1111 x = x & 0xdfef; 0x09cd

Bit negation (switching)

0010 0000 0001 0000 x = x ^ 0x2010;

x = x ^ 0x2010;

0x29cd (10701) 0x09dd (2525)

Negation of all bits 1111 1111 1111 1111 x = x ^ 0xFFFF; 0xf622

Negation of all bits x = ~x; 0xf622

Attention should be drawn to the strange behavior of exclusive or operator (^). If the exclusive or operation is carried out twice using the same mask the original value is returned, in this case 2525. This operation can be used for exchanging the values of two integer variables without the use of an auxiliary variable:

int m = 2, n = 7;

m = m ^ n;

n = m ^ n;

m = m ^ n;

A program error difficult to find is the result if the logical operators (!, &&, ||) used in conditions are interchanged with the bitwise operators (~, &, |).

3.7.2. Bit shift operations

Bit shift operators belong to another group of bit operations. Shift can be carried out either to the left (<<) or to the right (>>). During shifting the bits of the left side operand move to the left (right) as many times as the value of the right side operand shows.

In case of shifting to the left bit 0 is placed into the free bit positions, while the exiting bits are lost. However, shift to the right takes into consideration whether the number is signed or not. In case of unsigned types bit 0 enters from the left, while in case of signed numbers bit 1 comes in. This means that bit shift to the right keeps the sign.

short int x;

Value giving Binary value Operation Result

decimal (hexadecimal) binary

x = 2525; 0000 1001 1101 1101 x = x << 2; 10100 (0x2774)

0010 0111 0111 0100

x = 2525; 0000 1001 1101 1101 x = x >> 3; 315 (0x013b)

0000 0001 0011 1011

x = -2525; 1111 0110 0010 0011 x = x >> 3; -316 (0xfec4)

1111 1110 1100 0100 If the results are examined it can be seen that due to shift to the left with 2 bits the value of variable x increased four (22) times, while shift to the right with three steps resulted in a value decrease of x to its eighth (23). It can be stated generally that if the bits of an integer number is shifted to the left by n steps, the result is the multiplication of that number with 2n. Shift to the right by m bits means integer division by 2m. It is to be noted that this is the fastest way to multiply/divide an integer number with/by 2n.

In the example below the 16-bit integer number is divided into two bytes:

short int num;

unsigned char lo, hi;

// Reading the number

cout<<"\nPlease enter an integer number [-32768,32767] : ";

cin>>num;

// Determination of the lower byte by masking lo=num & 0x00FFU;

// Determination of the upper byte by bit shift hi=num >> 8;

In the last example the byte sequence of the 4 byte int type variable is reversed:

int n =0x12345678U;

n = (n >> 24) | // first byte is moved to the end, ((n << 8) & 0x00FF0000U) | // 2nd byte into the 3rd byte,

((n >> 8) & 0x0000FF00U) | // 3rd byte into the 2nd byte, (n << 24); // the last byte to the beginning.

cout <<hex << n <<endl; // 78563412

3.7.3. Bit operations in compound assigment

In language C++ a compound assignmnet operation also belongs to all the five two-operand bit operations, and this way the value of variables can be modified more easily.

Operator Relation sign Usage Operation

Operator Relation sign Usage Operation

Assignmnet by shift left <<= x <<= y shift of bits of x to the left with y bits,

Assignmnet by shift right >>= x >>= y shift of bits of x to the right with y bits,

Assignmnet by bitwise OR |= x |= y new value of x: x | y,

Assignmnet by bitwise AND

&= x &= y new value of x: x & y,

Assignmnet by bitwise exclusive OR

^= x ^= y new value of x: x ^ y,

It is important to note that the type of the result of bit operations is an integer type, at least int or larger than int, depending on the left side operand type. In case of bit shift any number of steps can be entered but the compiler uses the remainder created with the bit size of the type for shifting. For example, the phenomenon listed below is experienced in case of 32 bit int type variables.

unsigned z;

z = 0xFFFFFFFF, z <<= 31; // z ⇒ 80000000 z = 0xFFFFFFFF, z <<= 32; // z ⇒ ffffffff z = 0xFFFFFFFF, z <<= 33; // z ⇒ fffffffe