Binary operations in C

 

Most of the time, C is used like a high level language, in that the coding doesn`t really involve itself in the lower level programming, and worry about things like binary manipulation and registers.

However C is often used in embedded electronics, where a microprocessor is used to control some form of equipment, often without any human operator intervention.

In this use of C, it is usually necessary to include code for binary operations - a register may well be used for the storage of 8 separate flags used in the control of the equipment, rather than for the storage of a byte which is a piece of data. It therefore becomes necessary to write C code which can operate on each bit individually.

C isn`t really designed to look at and perform operations at bit level, the smallest quantity it is designed for is the byte. So working at bit level requires the use of bitwise manipulation and operations, which can result in the effective isolation and operation on individual bits.

This web page is therefore a look at some of the techniques within C that allow for binary operations, down to individual bits if required.

 

Number systems

By default, if you give C a number, it assumes that it is a decimal number - or a number to base 10.

The two alternatives are

Even if a number is written as a binary but without the "0b" - eg, 01001110, - C will still see it as a decimal number with the value of 1 million, 1 thousand, 1 hundred and ten.

NB - in the examples below I usually work on the fourth bit - I refer to bit 4 as the fourth bit, ie, the fourth bit from the right, which is the LSB end, as that is the end that bits are numbered from.


       MSB - 7 6 5 4 3 2 1 0 - LSB

        0b = 0 1 0 1 1 1 1 0  

                     ^
                     |
                     |    fourth bit

 

Setting a bit to "1"

If we have a variable or a register with an initial content of 0b00000000, at some point we will need to set one of the bits. This can be done using the logical OR operator - here we have the variable with a name of drive_reg, and we want to set the fourth bit to a "1".

The symbol for a logical OR is the "|" character.


        drive_reg=drive_reg | 0b00001000

For the fourth bit, we have OR`d the initial value of "0" with the value of "1", and the result is of course a "1".

For all the other bits, we have OR`d the initial value of "0" with the value of "0", so the result is still "0".

In fact it doesn`t matter what the initial value of each bit is, performing an OR operation with a "0" will preserve the initial value, and performing an OR operation with a "1" will always result in a "1".

Looking at it in binary, we get


          00000000
        | 00001000
         ----------
          00001000

Or with a non-zero starting value,


          01011110
        | 00001000
         ----------
          01011110

Finally, if you like shorthand coding, this operation can also be written as


        drive_reg|= 0b00001000

 

Setting a bit to "0"

This is done in a similar way, but instead of using an OR operator, we use the AND operator, and invert the mask - ie, each "1" becomes "0", and each "0" becomes "1" - so the mask is now 0b11110111.


        drive_reg=drive_reg & 0b11110111

It`s a bit confusing to have to rewrite the mask, so we can use the "~" operator to signify one`s complement of 0b00001000, so the statement now becomes


        drive_reg=drive_reg & ~0b00001000

If for example drive_reg contains 0b01011110, then in binary, this results in


        ~00001000 = 11110111  



          01011110
        & 11110111
         ----------
          01010110

So we have preserved each bit except the fourth bit, which has been changed to a "0".

The shorthand for this would be


        drive_reg&=~0b00001000

 

Extracting a single bit

To extract a single bit, we can AND the byte with the same mask.


        drive_reg=drive_reg & 0b00001000

If for example drive_reg contains 0b01011110, then in binary, this results in -


          01011110
        & 00001000
         ----------
          00001000

 

Toggling a bit

If we want to toggle a bit - ie - change its value from "1" to "0", or from "0" to "1", we can use the Exclusive OR operator, sometimes written as XOR - using the same mask to identify the bit to toggle.


        drive_reg=drive_reg ^ 0b00001000

In binary, we get


          01011110
        ^ 00001000
         ----------
          01010110

Doing it again, the bit will toggle back again.


          01010110
        ^ 00001000
         ----------
          00001000

 

Setting all the bits to zero

A simple way to reset all the bits in the variable or register to zero is to XOR the contents of the variable or register with themselves -


        drive_reg=drive_reg ^ drive_reg

In binary, we get


          01010110
        ^ 01011110
         ----------
          00000000

 

Setting all the bits to one

I think it should also be possible to set all the bits to "1" by doing an Exclusive OR with the one`s complement of itself -


        drive_reg=drive_reg ^ (~drive_reg)

In binary,


          01011110
        ^ 10100001
         ----------
          11111111

 

Toggling all the bits

If we want to toggle all the bits, this is simply done by using the NOT operator - which is the "~" character, or tilde. It has already been used a few times in this page, this just extends its use a bit.


        drive_reg=~drive_reg

In binary, the result is to toggle all the bits.


        ~ 01011110
        
         ----------
          10100001

 

Shifting bits sideways

Having extracted one specific bit, there are various ways we can proceed, in order to do something with this bit. One of these is to shift the wanted bit sideways - left or right - so that it sits somewhere else within the byte.

So if for example we wanted to shift it into bit 0, ie, the least significant bit, then we can use the >> operator to shift it three positions to the right.


        drive_reg=drive_reg >> 3

In binary,


         00001000  >> 3  =  00000001

This is useful as now the byte has a binary and decimal value of 1, so can more easily be used elsewhere.

 

Testing the value of a bit

Once the wanted bit is in the LSB position, with a value of either "0" or "1", we could test it to indentify its value, and make logical decisions based on the result.


         if(drive_reg==0b00000001)

             {

               do something;

             }

          else

             {

               do something else;

             }

Actually we don`t need to do all this to test the value of one bit. In many lanuages, the boolean state of true is indicated by a result of 1. However C will recognise a result of 1 or more as true, so C would accept a result of 0b00001000 as true. So we can test the state of a bit directly from the AND operation, without shifting to the right, and without even storing the result somewhere.


         if(drive_reg & 0b00001000)

             {

               do something;

             }

          else

             {

               do something else;

             }

Which is rather handy !

 

More on shifting sideways

The use described above of shifting the bits sideways rather understates the complexity of the sideways shifting operation.

To look at shifting sideways more fully, we need to consider several things -

In signed bytes, the MSB is used as a flag to indicate whether the byte contains a positive number or a negative number - "1" indicates a negative number, "0" indicates a positive number - the byte can therefore contain decimal values from -127 up to +127.

In unsigned bytes, all eight bits are used for the value, so an unsigned byte can contain decimal values from 0 up to 255.

For left shifts, a logical shift has the same effect as an arithmetic shift - the MSB or MSB`s is/are discarded, the bits are shifted left, and the one or more LSB`s are filled with zero`s.

For right shifts, a logical shift will discard the LSB or LSB`s, the bits are shifted, and zero`s are put in to the empty MSB`s.

For right shifts, an arithmetic shift will discard the LSB`s, the bits are shifted, and the content of the original MSB is put into the now empty MSB position or positions.

So for unsigned bytes, a logical shift is okay. But for signed bytes, an arithmetic right shift will preserve the positive/negative bit in the MSB position.

Some programme languages use a different operator symbol for right arithmetic shifts - they use >>>

However programmes like C, C++, Java, and JaveScript use the >> for both logical and arithmetic shifts, and in C, it is up to the compiler to decide how to perform the shift.

With C in an embedded environment, where the byte is being used to store up to eight flags, it is more likely that the byte will be unsigned, so logical shifting would be okay. So the above use of shifting is effectively a simplified representation of a logical shift on an unsigned byte.

 

Bit rotation

One of the problems with shifting bits sideways is that you lose bits - they get discarded. So if you shift one way to get a particular bit into a specific position, then shift back the other way to the original position, some of the bits have gone, and there is no way of knowing what they were.

One solution to this would be to rotate the bits - so that the bits that have been displaced from one end of the byte are used to fill in the empty spaces at the other end of the byte. So the value of each bit is preserved, but they now all sit in a different position.

Some programming languages know about rotation, but C isn`t one of them - C doesn`t have an operator for rotation.

However all is not lost, as it appears that it is possible to create a rotation operation using a complex combination of shifts. For a variable called c, rotating n bits to the right, the rotation operation can be achieved with


        c= (c >> n) | (c << (8-n) )

ie, we are using an OR operation to combine a right shift of n spaces with a left shift of (8-n) spaces.

In binary, for a right shift of one space of our variable drive_reg, it looks like


        drive_reg      =   01011110

        drive_reg >> 1 =   00101111      // the empty space at the left end is filled with a "0"

        drive_reg << 7 =   00000000      // the 7 empty spaces at the right end are filled with "0"

               ----------

        |              =   00101111      // the OR operation  

Again, I think that there is an assumption that the variable contains an unsigned value - the compiler may be a bit sniffy about trying to do it with a signed value, as it would have to worry about logical or arithmetic shifts.

Thinking some more about it, bit rotation won`t work with a signed value, because the technique relies on the zero`s that are put into the empty spaces - during the OR operation these zero`s mean that the result of the OR operation is the same as the value in each position after the shifts. If the shift operation was done in such a way that the sign bit in the MSB position was preserved, it would muck up the OR operation.

After the two shift operations, for each of the 8 bit positions, one of the results of the shift operations will have a "0" put there by the infill of empty spaces. If the sign bit is preserved, one or more of these infill values may be a "1", so the OR operation will not produce the correct result.

 

 

 

 

 

website design by ron-t

 

website hosting by freevirtualservers.com

 

© 2024   Ron Turner

 

+                                   +  

 

Link to the W3C website.   Link to the W3C website.