Bit Processing in C#

By | March 5, 2015

As a .NET programmer, how do you check if a bit is set within a byte? How do you set, unset or toggle a bit? How do you get the binary string representation of a byte? Well for most of those questions, the answer is to use the same bit-twiddling methods that C/C++ programmers are likely familiar with. Thankfully C# supports bitwise operators so we can leverage these existing methods. Towards the end of this article these methods are summed up into a C# class of Extension Methods.

1. Checking if a bit is set at a particular position.

We use the expression (myByte & (1 << position)) != 0 to check if a bit is set. This works by using the Left Shift operator (<<) to take the value of 1 whose binary expression is suprisingly (heavy sarcasm) 00000001 to shift the bit to the index (0-7) which we want to check.

Applying the Left Shift operator on the value of 1 looks like this:

1 << 0 = 00000001
1 << 1 = 00000010
1 << 2 = 00000100
1 << 3 = 00001000
1 << 4 = 00010000 
1 << 5 = 00100000
1 << 6 = 01000000
1 << 7 = 10000000

Performing a bitwise AND (&) of the resulting value and the byte will mask out all of the bits we do not care about and unveil to us only the value which we want to check for nonzero. Recall that with bitwise AND both bits must be 1 for the result to be 1, otherwise the result of the operation is 0. If the bit is unset at the specified position then the result of applying the mask will yield a value of zero, otherwise we will get a nonzero value. Hence, that is why we are checking to see if the value is nonzero.

An example:

  00100000
& 11100101
----------
  00100000 =/= 0 (Therefore, the bit at index 5 is set!)

2. Setting a bit at a particular position.

To set a bit we use the expression myByte | (1 << position). The Left Shift operation on the value of 1 has the same effect as before and this time we perform a bitwise OR (|) on the byte in question and the mask. Remember that for a bitwise OR operation between two bits - If one or both of the two bits is 1 then the result is 1, otherwise the result is 0. Therefore this will leave the other bits in their current state and set the bit at the specified position (if it is not already set).

An example:

 
  00000100
| 11010010
----------
  11010110 (Look at them apples! Bit at index 2 has been set!)

3. Unsetting a bit at a particular position.

To unset a bit we use the expression myByte & ~(1 << position). At this point, you are probably noticing a pattern with the Left Shift operation being performed on the value of 1 - In fact every bit twiddling method in this article relies on it. We perform a bitwise NOT (~) on the resulting value from applying the Left Shift operation. The bitwise NOT inverts the bits - Any digit that is a 1 becomes a 0 and any digit that is a 0 becomes a 1.

The result of applying the bitwise NOT (negation):

~(1 << 0) = 11111110
~(1 << 1) = 11111101
~(1 << 2) = 11111011
~(1 << 3) = 11110111
~(1 << 4) = 11101111 
~(1 << 5) = 11011111
~(1 << 6) = 10111111
~(1 << 7) = 01111111

Now we perform a bitwise AND which will have the effect of leaving all bits in their current setting except the bit at the position we have specified (since 1 x 0 = 0 and 1 x 1 = 1). The bit at the specified position will be bitwise ANDed with a 0 which will always result in a value of 0 at that position.

An example:

 
  11111011
& 11100101
----------
  11100001  (Wowzers! Bit at index 2 has been unset!)

4. Toggling a bit at a particular position.

To a toggle a bit at a particular index we use the expression myByte ^ (1 << position). Now the bitwise XOR (Exclusive OR) operation has the effect of resulting in a value of 1 whenever the two bits are opposite in value, otherwise if the values are the same then the result is 0. Applying the XOR operation on the mask and the byte, preserves the bits at the positions we do not care about - As the set bits will be XORed against unset bits which results in a set bit and the unset bits will be XORed against an unset bit which results in an unset bit. Now what is more interesting is what occurs at the bit position we want to toggle. For a bit which is set, it will be XORed against a set bit resulting in an unset bit! For a bit which is unset, it will be XORed against a set bit which results in a set bit! Pretty cool, huh?

Let's see an example:

  00000001
^ 11010010
----------
  11010011 (Bit at index 0 has been toggled!)

5. Getting the binary string representation of a byte in C#.

So this method utilizes Convert.ToString(Byte, Int32) which takes a byte and converts it to a string in the specified base. In this case, we want the binary (base 2) representation. Done? Not quite, the string will not have leading zeroes so the most significant bits (MSBs) will be excluded when zero. I personally do not prefer this so I use String.PadLeft(Int32, Char) to pad the string to the left with zeroes until the binary string is 8 characters in length. The resulting C# code is this:

Convert.ToString(myByte, 2).PadLeft(8, '0');

Byte type Extension Methods Class

Below is a C# class of Byte Extension Methods which encompass the techniques just discussed.

/*
Simplified BSD License

Copyright 2017 Derek Will

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 
in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
OF SUCH DAMAGE.
*/

using System;

namespace ByteExtensionMethods
{
    public static class ByteExtensions
    {
        public static bool IsBitSet(this byte b, int pos)
        {
            if (pos < 0 || pos > 7)
                throw new ArgumentOutOfRangeException("pos", "Index must be in the range of 0-7.");

            return (b & (1 << pos)) != 0;
        }

        public static byte SetBit(this byte b, int pos)
        {
            if (pos < 0 || pos > 7)
                throw new ArgumentOutOfRangeException("pos", "Index must be in the range of 0-7.");

            return (byte)(b | (1 << pos));
        }

        public static byte UnsetBit(this byte b, int pos)
        {
            if (pos < 0 || pos > 7)
                throw new ArgumentOutOfRangeException("pos", "Index must be in the range of 0-7.");

            return (byte)(b & ~(1 << pos));
        }

        public static byte ToggleBit(this byte b, int pos)
        {
            if (pos < 0 || pos > 7)
                throw new ArgumentOutOfRangeException("pos", "Index must be in the range of 0-7.");

            return (byte)(b ^ (1 << pos));
        }

        public static string ToBinaryString(this byte b)
        {
            return Convert.ToString(b, 2).PadLeft(8, '0');
        }
    }
}

Let's see these Extension Methods in action with the following Console Application:

class Program
{
    static void Main()
    {
        byte myByte = 0x2a;

        Console.WriteLine("*** Byte Extension Methods ***");
        Console.WriteLine("Initial Value: {0}", PrintInfo(myByte));
        Console.WriteLine(string.Empty);

        Console.WriteLine("----- Testing Bit Status Check Methods -----");
        Console.WriteLine("Is Bit 3 set: {0}", myByte.IsBitSet(3));
        Console.WriteLine("Is Bit 6 set: {0}", myByte.IsBitSet(6));

        Console.WriteLine(string.Empty);
        Console.WriteLine("----- Testing Bit Manipulation Methods -----");
        Console.WriteLine("Initial For Reference: {0}", PrintInfo(myByte));

        myByte = myByte.SetBit(4);
        Console.WriteLine("After Setting Bit 4  : {0}", PrintInfo(myByte));

        myByte = myByte.UnsetBit(5);
        Console.WriteLine("After Unsetting Bit 5: {0}", PrintInfo(myByte));

        myByte = myByte.ToggleBit(0);
        Console.WriteLine("After Toggling Bit 0 : {0}", PrintInfo(myByte));

        myByte = myByte.ToggleBit(4);
        Console.WriteLine("After Toggling Bit 4 : {0}", PrintInfo(myByte));

        Console.WriteLine(string.Empty);
        Console.WriteLine("----- Testing Exception Throwing -----");
        try
        {
            myByte.IsBitSet(-3);
        }
        catch (ArgumentOutOfRangeException ex)
        {
            Console.WriteLine("Caught {0}: {1}", ex.GetType().Name, ex.Message);
        }

        Console.WriteLine(string.Empty);
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

    static string PrintInfo(byte b)
    {
        return string.Format("Byte = {0:x2} | Binary String = {1}", b, b.ToBinaryString());
    }
}

This produces the following output:

Byte Extension Methods in Action

The Extension Methods class and Console Application sample are available to download here.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.