Reading a Wide Bit Field

The same programming kung fu used to read a single bit also applies to wide bit fields. The process involves masking and shifting: You must know the bit field’s width and position in an integer value. Once those values are obtained — no matter how wide the bit field — a single function handles the job.

The function I crafted to read a wide bit field is called bit_field_read(). Here is its prototype:

unsigned char bit_field_read(char bit, char width, char byte);

The argument bit is the position in the integer value, here limited to unsigned char variables. Position 0 is the far right bit and the values range up to 7 for the far left bit.

width is the size of the chunk to read, which can range from 1 to read a single bit or 8 to read the entire byte.

Finally, byte is the value containing the bit field.

My function doesn’t do any error checking, so it’s possible to read a bit not within the byte or a chunk wider than the byte. If I were coding bit_field_read() for use “in the wild” I would definitely add such bounds checking.

What the bit_field_read() function does is to shift bits in the byte right so that the field starts at bit 0. Then the field width is masked. The result is the value desired. Figure 1 demonstrates how a bit field 4-bits wide at position 2 would be processed by the bit_field_read() function.

Figure 1. Processing a field 4-bits wide.

Figure 1. Processing a field 4-bits wide.

The tough part of the function is to convert the width value into a proper bit mask. For example, the value 2 specifies a width of two bits, but in binary the value 2 is 10 and the mask should be 11, which is decimal 3. Table 1 shows how the width values need to be converted.

Field width Binary mask Decimal value (unsigned)
1 0000-0001 1
2 0000-0011 3
3 0000-0111 7
4 0000-1111 15
5 0001-1111 31
6 0011-1111 63
7 0111-1111 127
8 1111-1111 255

To solve the puzzle, you must get from a value like 3 to the value 7. The solution I devised uses binary manipulation to add a bit to a byte, shift the value left, then keep looping to add bits. Here is that code snippet:

    mask = 0;
    while(width--)
    {
        mask = mask << 1;
        mask |= 1;
    }

The variable width holds the original width value, such as 3. mask holds the final value, such as 7. The mask starts as 0. Its bits are shifted right, which has no effect when the loop begins. Then the mask |= 1 statement sets bit 0. If the value of width is larger, the loop continues. Bit 0 is shifted to bit 1, then bit 0 is set again. Figure 2 illustrates this process.

Figure 2. How decimal widths are converted into binary masks.

Figure 2. How decimal widths are converted into binary masks.

In the end, the width value is converted the proper mask value, just as shown in Table 1. The mask value is used in the bit_field_read() function to properly mask bits in the given integer value.

Here is a demo program that does the same conversions as shown in last week’s Lesson, though the bit_field_read() function is used instead:

#include <stdio.h>

char bit_field_read(char bit, char width, char byte);

int main()
{
    unsigned char board;

    board = 23;

    printf("The Queen is at row %d, column %d\n",
            bit_field_read(3,3,board)+1,
            bit_field_read(0,3,board)+1
            );

    return(0);
}

char bit_field_read(char bit, char width, char byte)
{
    unsigned char mask;

    mask = 0;
    while(width--)
    {
        mask = mask << 1;
        mask |= 1;
    }

    byte = byte >> bit;
    return( byte & mask );
}

The output from the code is the same as last week:

The Queen is at row 3, column 8

The bit_field_read() function works on any size field from 1 bit wide to the entire byte. So the function can also be used to read on-off values similar to the bit_test() function from an earlier Lesson.

A bit_field_write() function involves a bit more manipulation to put a bit field into an integer value. I’ll demonstrate that function in next week’s Lesson.

Leave a Reply