Read non-byte-aligned value from byte array

77 Views Asked by At

I am trying to read a n-bit (n <= 8) long value from a byte[]. This value may not be aligned to the start/end of any byte or may span two bytes. To make this more clear, please see the following example:

//                                 __     ___             ## #
var bytes = new byte[] { 0b_1111_1100, 0b_0001_1111, 0b_1100_0111 };

I am interested in reading the 5 zeroes spanning two bytes (marked with _) as a byte/int and the second span of 3 bits (marked with #). Obviously, the content of these bits is arbitrary, they may not all be zero.

I know that I could read the content of these bytes and calculate the value of these bits alone from there, but this seems very cumbersome to me, as it requires a lot of work to simply read some bits. The following snippet is a naive implementation of this approach:

var bytes = new byte[] { 0b_1111_1100, 0b_0001_1111, 0b_1100_0111 };
Console.WriteLine("5bit value: " + GetNBitsFromByteArray(bytes, 0, 6, 5));
Console.WriteLine("3bit value: " + GetNBitsFromByteArray(bytes, 2, 2, 3));

int GetNBitsFromByteArray(byte[] bytes, int startByte, int startBit, int length)
{
    if (startBit + length <= 8)
    {
        // The entire value is in one byte
        return GetBitsFromByte(bytes[startByte], startBit, length);
    }
    else
    {
        // The value is split over two bytes
        var lengthInByte1 = 8 - startBit;
        var part1 = GetBitsFromByte(bytes[startByte], startBit, lengthInByte1);
        var part2 = GetBitsFromByte(bytes[startByte + 1], 0, length - lengthInByte1);
        return (part1 << lengthInByte1) + part2;
    }
}

// startBit is the index of the bit where the selection should start.
// 0 is the highest bit, 7 is the lowest bit. e.g. GetBitsFromByte(0b_0001_1110, 3, 4) == 15
int GetBitsFromByte(byte byteValue, int startBit, int length)
{
    var mask = (1 << 8 - startBit) - (1 << 8 - startBit - length);
    return byteValue & mask;
}

Another approach is to utilise the BitArray class. This type does not seem to provide methods to do the actions described above either and adding the bits (see example) does not seem very elegant either:


BitArray bits = new(new byte[] { 0b_1111_1100, 0b_0001_1111, 0b_1100_0111 });
Console.WriteLine("First ones: " + Get5BitValue(bits, 6));
Console.WriteLine("Second ones: " + Get3BitValue(bits, 18));

int Get5BitValue(BitArray bits, int startBitIndex)
{
    return (bits[startBitIndex + 0] ? 16 : 0) +
           (bits[startBitIndex + 1] ? 8 : 0) +
           (bits[startBitIndex + 2] ? 4 : 0) +
           (bits[startBitIndex + 3] ? 2 : 0) +
           (bits[startBitIndex + 4] ? 1 : 0);
}
int Get3BitValue(BitArray bits, int startBitIndex)
{
    return (bits[startBitIndex + 0] ? 4 : 0) +
           (bits[startBitIndex + 1] ? 2 : 0) +
           (bits[startBitIndex + 2] ? 1 : 0);
}

I know I could use something like GetNBitValue(BitArray bits, int startBitIndex, int n) using a loop, but to improve legibility I have chosen to include this example.

Is there any more elegant or performant approach?

PS: all code is written and tested in dotnet-7.

0

There are 0 best solutions below