HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Reading bits from a stream from least to most significant

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
streamreadingbitsleastfrommostsignificant

Problem

For a recent project I was in need of excessive bit manipulation. Specifically, I had to write to or read from a variable number of bits up from the least significant bit towards the most significant bit.

I created the following helper class since I had spare time and wanted to learn something. I made it work but feel very unsure about whether it's performance might be sub optimal.

/*
 * This class provides functions to read or write 1 to 8 bits to a stream at a time
 * The bits a read from the least significant to the most significant
 * 
 */
public class BitStreamer : IDisposable {
    /*
     * Initializes a BitStreamer on a source Stream with a bps of 1
     */
    public BitStreamer(Stream source) : base(source, 1) { }

    public BitStreamer(Stream source, int bps) {
        if(bps  8) 
            throw new ArgumentException("The bits per sample has to be in the range of a byte (1 to 8)");
        m_strSource = source;
        m_iBitsPerSample = bps;
        m_iPosition = 0;
        m_iLength = source.Length;
        m_bEndOfStream = false;
    }
    /* Bits per sample */
    private int m_iBitsPerSample;

    /* Base stream */
    private Stream m_strSource;

    /* Binary stream helpers */
    private BinaryReader m_bstrSrc;
    private BinaryWriter m_bstwSrc;

    /* Byte stream fields */
    private bool m_bEndOfStream;
    private long m_iPosition;
    private long m_iLength;

    /* Bit stream fields */
    private byte m_bCurrentByte;
    private byte m_bInBytePosition;
    private byte m_bCurrentBits;


Methods for bit writing

```
/*
* Write up to 8 bits to the current buffer
*/
public void WriteBits(byte b) {
m_bCurrentBits = 0;
for(int i = 0; i 7)
WriteCurrentByte();

byte tar = m_bCurrentByte;
byte nbt = (byte)((tar & ~(1 << m_bInBytePosition)) | (bit << m_bInBytePosition));
m_bCurrentByte = nbt;
m_bInBytePosition += 1;
}

/*

Solution

Here are my suggestions:

Basic stuff

  • favor naming conventions beginning by an underscore instead of the C++ m_



  • stay away from that practice of specifying the type in 1st letter like iBitsPerSample



  • not sure you really need that IDisposable pattern, Stream is a managed object



  • this can be an endless debate but there's no real need to nullify fields, throwing a ObjectDisposedException is simpler and more informative to the user



  • using try ... catch is probably overkill



  • IMO there are twice as much properties/fields as one might ever need



  • you should not restrain the user to be able to read only 1 to 8 bits at a times



Intermediate

By trying to code some implementation for you I first thought deriving from Stream but this proved to not be the best approach since the lowest common denominator for all its derived is byte, but you want bits.

Thinking a bit, following idea came out, do like BinaryReader.

This has many advantages :

  • you offload the reading/writing to any possible type of stream (memory, network, file etc ...)



  • the reading/writing logic is separated from any stream implementation



  • makes things simpler in the end



In short, craft BitReader/Writer classes !

Here's my code, feel free to improve it:

public class BitReader : IDisposable
{
    private readonly bool _leaveOpen;
    private readonly Stream _stream;
    private int _byte;
    private bool _canRead;
    private int _counter;

    public BitReader(Stream stream, bool leaveOpen = true)
    {
        if (stream == null) throw new ArgumentNullException("stream");
        _stream = stream;
        _leaveOpen = leaveOpen;
    }

    public void Dispose()
    {
        if (!_leaveOpen)
        {
            _stream.Dispose();
        }
    }

    public int Read(byte[] buffer, int count)
    {
        // simple wrapper to read many bits at once
        var bits = Math.Min(count, buffer.Length);
        for (var i = 0; i > _counter) & 1;
        _counter++;
        if (_counter > 7)
        {
            _counter = 0;
            _canRead = false;
        }

        bit = (byte) value;
        return true;
    }
}


Notes:

  • i use like Stream.Read, an integer to tell how many bits have been read into user's buffer



  • i use a bool to signal whether a bit read has been successful or not



Now some demo :D

public class Demo
{
    public Demo()
    {
        var random = new Random(0);
        var buffer = new byte[100];
        random.NextBytes(buffer);

        using (var stream = new MemoryStream(buffer))
        using (var reader = new BitReader(stream))
        {
            // read bits one by one
            var bits = new List();
            for (var i = 0; i ();
            for (var i = 0; i  value |= s << t).ToArray(); // lazy way :)
                verify.Add((byte) value);
            }

            var sequenceEqual = verify.SequenceEqual(buffer);
            if (!sequenceEqual)
            {
                throw new InvalidDataException();
            }
        }
    }
}


TODO:

  • code a writer !



  • implement some useful properties you'd like to have like BitsRead etc ...



Advice

Code only what you really need, do not try to think for the future but only about what you need right now.

Optimization should take place at the end and only if really needed (do some profiling first).

Learn to be lazy when programming, often it leads to better solutions by their simplicity yet highly efficient. Overthinking is not always the best thing to do.

Bonus:

Here's some endianness-aware reader I needed for a game, in the beginning it was bulky/inefficient then I started it from scratch by simply implementing what I really needed. Over time I upgraded it by adding overloads, until some pattern came out. As you can see there are hub methods that do most of the work.

This is not a direct help to your current work but a hint of how laziness and clarity is helpful, by being lazy you won't waste more time than a thing deserves and by being clear your code can be upgraded more easily.

```
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

// ReSharper disable RedundantTypeArgumentsOfMethod

namespace Assets.Scripts.Extensions
{
public static class BinaryReaderExtensions
{
public enum Endianness
{
LittleEndian,
BigEndian
}

private static readonly Endianness PlatformEndianness = BitConverter.IsLittleEndian
? Endianness.LittleEndian
: Endianness.BigEndian;

public static string ReadStringAscii(this BinaryReader reader, int length)
{
var bytes = reader.ReadBytes(length);
var s = Encoding.ASCII.GetString(bytes);
return s;
}

public static string ReadStringAsciiNullTerminated(this BinaryReader reader)
{
var list = new List();
byte b;
while ((b = reader.ReadByte())

Code Snippets

public class BitReader : IDisposable
{
    private readonly bool _leaveOpen;
    private readonly Stream _stream;
    private int _byte;
    private bool _canRead;
    private int _counter;

    public BitReader(Stream stream, bool leaveOpen = true)
    {
        if (stream == null) throw new ArgumentNullException("stream");
        _stream = stream;
        _leaveOpen = leaveOpen;
    }

    public void Dispose()
    {
        if (!_leaveOpen)
        {
            _stream.Dispose();
        }
    }

    public int Read(byte[] buffer, int count)
    {
        // simple wrapper to read many bits at once
        var bits = Math.Min(count, buffer.Length);
        for (var i = 0; i < bits; i++)
        {
            byte bit;

            if (!Read(out bit))
            {
                return i;
            }

            buffer[i] = bit;
        }
        return bits;
    }

    public bool Read(out byte bit)
    {
        // do we need to provision our bit buffer ?
        if (!_canRead)
        {
            _byte = _stream.ReadByte();
            _canRead = true;
        }

        // we are at EOF
        if (_byte == -1)
        {
            bit = 0;
            return false;
        }

        // get current bit and update our counter
        var value = (_byte >> _counter) & 1;
        _counter++;
        if (_counter > 7)
        {
            _counter = 0;
            _canRead = false;
        }

        bit = (byte) value;
        return true;
    }
}
public class Demo
{
    public Demo()
    {
        var random = new Random(0);
        var buffer = new byte[100];
        random.NextBytes(buffer);

        using (var stream = new MemoryStream(buffer))
        using (var reader = new BitReader(stream))
        {
            // read bits one by one
            var bits = new List<byte>();
            for (var i = 0; i < buffer.Length*8; i++)
            {
                byte bit;
                if (reader.Read(out bit))
                {
                    bits.Add(bit);
                }
            }

            // check that our thing worked
            var verify = new List<byte>();
            for (var i = 0; i < 100; i++)
            {
                var value = 0;
                bits.Skip(i*8).Take(8).Select((s, t) => value |= s << t).ToArray(); // lazy way :)
                verify.Add((byte) value);
            }

            var sequenceEqual = verify.SequenceEqual(buffer);
            if (!sequenceEqual)
            {
                throw new InvalidDataException();
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

// ReSharper disable RedundantTypeArgumentsOfMethod

namespace Assets.Scripts.Extensions
{
    public static class BinaryReaderExtensions
    {
        public enum Endianness
        {
            LittleEndian,
            BigEndian
        }

        private static readonly Endianness PlatformEndianness = BitConverter.IsLittleEndian
            ? Endianness.LittleEndian
            : Endianness.BigEndian;

        public static string ReadStringAscii(this BinaryReader reader, int length)
        {
            var bytes = reader.ReadBytes(length);
            var s = Encoding.ASCII.GetString(bytes);
            return s;
        }

        public static string ReadStringAsciiNullTerminated(this BinaryReader reader)
        {
            var list = new List<byte>();
            byte b;
            while ((b = reader.ReadByte()) != 0)
            {
                list.Add(b);
            }
            var s = Encoding.ASCII.GetString(list.ToArray());
            return s;
        }

        #region Numbers (with offset)

        private static uint ReadUInt32(BinaryReader reader, Endianness endianness, long offset)
        {
            return ReadAt(reader, offset, () => reader.ReadUInt32(endianness));
        }

        public static uint ReadUInt32BE(this BinaryReader reader, long offset)
        {
            return ReadUInt32(reader, Endianness.BigEndian, offset);
        }

        public static uint ReadUInt32LE(this BinaryReader reader, long offset)
        {
            return ReadUInt32(reader, Endianness.LittleEndian, offset);
        }

        public static void SetPosition(this BinaryReader reader, long offset)
        {
            reader.BaseStream.Position = offset;
        }

        private static T ReadAt<T>(BinaryReader reader, long offset, Func<T> func)
        {
            reader.SetPosition(offset);
            var t = func();
            return t;
        }

        #endregion

        #region Numbers (with endianness)

        public static short ReadInt16(this BinaryReader reader, Endianness endianness)
        {
            var i = reader.ReadInt16();
            return Convert(i, endianness);
        }

        public static int ReadInt32(this BinaryReader reader, Endianness endianness)
        {
            var i = reader.ReadInt32();
            return Convert(i, endianness);
        }

        public static ushort ReadUInt16(this BinaryReader reader, Endianness endianness)
        {
            var i = reader.ReadUInt16();
            return Convert(i, endianness);
        }

        public static uint ReadUInt32(this BinaryReader reader, Endianness endianness)
        {
            var i = reader.ReadUInt32();
            return Convert(i, endianness);
        }

        private static short Convert(short i, Endianness endianness)
        {
            return endianness == PlatformEndianness ? i : Reverse(i);
        }

        pr

Context

StackExchange Code Review Q#100958, answer score: 5

Revisions (0)

No revisions yet.