Recent Activity

Twiddling Bits in VB.NET

Setting and reading individual bits (aka bit masking) is a powerful tool and something I've always had a hard time with. Tutorials and documentation explain how to set and read bits, but they seem to fall short in explaining what is actually going on under the covers.

Binary Basics

All data in a computer is represented as bits. In VB.NET the Integer type is a 32-bit integer and is represented internally as 32 bits. For example:

Dim i As Integer = 4

Internally, the variable i is represented as the following 32 bits:

Bits: 0000 0000 0000 0000 0000 0000 0000 0100 Index: 31 0

Each bit has an index, starting from 0 at the right and ending with 31 at the left. The value of each bit is 2^x, where x is the bit's index in the bit string, so the right-most bit (more commonly known as the Least Significant bit) has a decimal value of 2^0 or 1. The left-most bit (or Most Significant bit) has a decimal value of 2^31 or 2,147,483,648.

The value of the number is the sum of all "1" bits in the string. For example:

[ 0111 ] = 2^2 + 2^1 + 2^0 = 4 + 2 + 1 = 7

VB's Bit Operators

Visual Basic provides the tools needed to start editing individual bits, namely the "bitwise" logical operators. The following is a list of bitwise operators along with a basic idea of their intended purpose. The first three operators below (And, Or, XOr) enable you to use two bit strings to produce a new third string. The last operator (Not), operates on just one bit string alone.

And - Lets you check for a specific pattern Or - Turns on a specified pattern, leaving other bits alone XOr - Chooses one flag or the other, but not both Not - Inverts all bits (1 becomes 0, and vice versa)

Here are a few examples of two given input strings along with the output created. These examples show what happens conceptually, actual VB code would not have literal binary strings.

1111 And 1010 = 1010 1010 And 0101 = 0000 1000 Or 0001 = 1001 1010 Or 1011 = 1011 1000 XOr 1010 = 0010 1111 XOr 1111 = 0000 Not 1001 = 0110 Not 1111 = 0000 Not 0000 = 1111

As mentioned earlier, in real VB code the bit string is hidden in the Integer data type. The bitwise operators can be used to see the actual contents of these bit strings. The first thing we need is a function that displays the bit string of a given Integer.

Sub PrintBinary(ByVal number As Integer) Dim bpi As Integer = CInt(Math.Log(Integer.MaxValue, 2)) Console.WriteLine("Bits/Integer: " & bpi) For i As Integer = bpi To 0 Step -1 Dim bit As Char If (number And CLng(2 ^ i)) > 0 Then bit = "1"c Else bit = "0"c End If Console.Write("{0} ", bit) If i > 0 And i Mod 4 = 0 Then Console.Write(" ") End If Next Console.WriteLine() End Sub

The first line of this code establishes the number of bits in an integer by taking the log base 2 of the maximum integer value, which returns ~30.99 and is rounded up to 31 by CInt.

(Side note: the reason 30.99 is returned instead of 31 is because the max value of a signed integer is actually 1 less than the value of the most significant bit when negatives are implemented using 2's complement. Understanding this is not critical to the rest of this post.)

Next a loop is created to print out each bit from most significant (left) to least significant (right). Each iteration of the loop, the number is And'ed with the current value of the bit in position i, which is represented by 2 to the i'th power. This integer value of 2^i creates a bit string with only 1 bit turned on (all other bits are set to zero) and is used to test the same bit in the given number by applying the And operator.

If this And operation returns zero, the bit in the i'th position of the given number is off, if it returns non-zero, the bit is on. Below is an trace of the loop showing what is actually happening at the bit level when i=1 and i=2. A call to PrintBinary(4) is assumed.

The number passed in by the user: number = 4 = 0000 0000 0000 0000 0000 0000 0000 0100 First iteration (the bit is off): i = 1 2^i = 2 = 0000 0000 0000 0000 0000 0000 0000 0010 If (number And CLng(2 ^ 1)) > 0 Then 4 And 2 > 0 (?) = 0000 0000 0000 0000 0000 0000 0000 0000 = 0 '<- Result, False Second iteration (the bit is on): i = 2 2^i = 4 = 0000 0000 0000 0000 0000 0000 0000 0100 If (number And CLng(2 ^ 2)) > 0 Then 4 And 4 > 0 (?) = 0000 0000 0000 0000 0000 0000 0000 0100 = 4 '<- Result, True

As shown in the two iterations above, the And operator compares the bits in a string 1-by-1 matched up by index. If the bits are both on, the resulting bit in the i'th position in the new bit string is also on. The final result is returned as an Integer.

This becomes more interesting when using the Or operator to create a number with multiple bits turned on. The key is to use numbers that are an integer power of 2, any other numbers will mangle the bit string. Here is an example of setting 3 bits and passing it to our PrintBinary() function:

' Note that ' 128 = 2 ^ 7 ' 4 = 2 ^ 2 ' 2 = 2 ^ 1 PrintBinary(128 Or 4 Or 2) = 0000 0000 0000 0000 0000 0000 1000 0110

Notice how the bit positions match exactly the powers of 2 that were passed to PrintBinary, that is bits 7, 2 and 1 are flipped on.

Real World Bit Strings

In a real application, it is desirable to simplify this process as much as possible; enter enumerations. An enumeration can be used to give names to individual bits and can then be used in a function as a set of flags or options.

Public Enum FileFlags As Integer Read = 1 Write = 2 Execute = 4 End Enum

Notice that the enumeration values are integer powers of 2, same as above. This enumeration defines 3 flags, Read, Write and Execute. A function can use these flags to indicate how it should operate. This is only appropriate when the flags are not mutually exclusive (meaning more than one flag can be applied at the same time).

To use this enumeration, I have created a dummy function called "SetPermissions". The function is not complete; it is only intended to demonstrate how to read bit flags by using a bit mask.

Sub SetPermissions(ByVal flags As FileFlags) If (flags And FileFlags.Execute) = FileFlags.Execute Then Console.Write("X") Else Console.Write("-") End If If (flags And FileFlags.Read) = FileFlags.Read Then Console.Write("R") Else Console.Write("-") End If If (flags And FileFlags.Write) = FileFlags.Write Then Console.Write("W") Else Console.Write("-") End If End Sub SetPermissions(FileFlags.Read Or FileFlags.Write) => -RW

What is actually happening is that when SetPermissions is called, the "Or" operator is turning on the Read bit and the Write bit in our bit string. The function then applies a bit mask using the "And" operator to test if each individual flag is set.

A Few Words on Style

Enumerations are preferred when multiple options are available for one specific value. They should not be used when the values are logically disconnected.

Constants can be used instead of enumerations, however, enumerations are preferred when the values logically group together (constants also disable intelisense). The Win32 API is a good example of values that should be grouped when ported to .NET. In the C/C++ headers, all values are represented as individual constants, converting them to an enumeration when porting makes working with the API much nicer.

As always, never under any circumstance should literal numbers be used to create a flag. So-called magic numbers without names will confuse the hell out of anyone working on the code after you have written it - yes, including you :). All literal numbers should be given names, either as enumerations, constants, or at the very least in a local variable.

Download the bit string example code for this article.

Posted in VB.NET on June 24th

Comments

Post a Comment
 
(private)