Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Encode / Decode n-bit Integers into byte[]

0.00/5 (No votes)
1 May 2014 1  
The class allows you to encode / decode 4, 9 or 17 bit (for example, 1 through 31 bit unsigned int supported) integers to and from a byte[].

Introduction

A pain to write, and my Google-fu was not strong enough to find someone who'd done it for me.

The class allows you to encode / decode 4, 9 or 17 bit (for example) integers to and from a byte[]. I needed it to store some binary keys in a database that are made up of multiple keys, in my case the difference between a byte[] storing 9 bit integers and one storing 16/32 bit integers would be very costly. If you are doing something similar in MSSQL, watch out for 0 byte right truncation (reserve the value of 0 perhaps).

This version of the code supports 1 though 31 bit unsigned integers, it could easily be extended beyond this if needed.

Using the Code

Use freely, keep attributions, no warranty as to usefulness or safety/reliability of the code of course.

using System;
 
namespace MhanoHarkness
{
    /// <summary>
    /// Encode and decode n (1..31) bit integers to / from byte arrays.
    /// 
    /// Converts and array of integers into a byte[] encoded to a 
    /// specific number of bits per integer. Useful when every bit of
    /// storage matters, for example storing a series  of 9 bit integers 
    /// as a byte[] instead of 16 or 32 bit ints could halve or more the 
    /// size of a database in some special cases!
    /// 
    /// Written by Mhano Harkness, special mention to Tim for some 
    /// great help :-)
    /// </summary>
    public class BitIntConverter
    {
        /// <summary>
        /// May return more ints than the number encoded due to padding.
        /// You should reserve the value of 0 to cater for detecting this.
        /// </summary>
        /// <param name="data">byte array encoded by GetBytesFromInts</param>
        /// <param name="bitsPerInt"># used per integer when encoding with
        ///                          GetBytesFromInts</param>
        /// <returns></returns>
        public static int[] GetIntsFromBits(byte[] data, int bitsPerInt)
        {
            if (bitsPerInt < 1 || bitsPerInt > 31)
            {
                throw new ArgumentOutOfRangeException("bitsPerInt", 
                    "1..31 bit unsigned integers supported only.");
            }
 
            var result = new int[(data.Length * 8) / bitsPerInt];
 
            for (var i = 0; i < result.Length; i++)
            {
                var startBit = i * bitsPerInt;
                var startByte = (startBit / 8);
                var endBit = (startBit + bitsPerInt);
                var endByte = (endBit-1) / 8;
                var byteRange = endByte - startByte;
 
                for (var b = 0; b <= byteRange; b++)
                {
                    var bi = startByte + b;
                    Int64 byteAsI64 = data[bi];
                    var amountToShift = (b*8) - (startBit%8);
                    var shiftedForMerge = amountToShift < 0 ? 
                byteAsI64 >> (0 - amountToShift) :
                byteAsI64 << amountToShift;
                    result[i] |= (int)shiftedForMerge;
                }
 
                result[i] &= (1 << bitsPerInt) - 1;
            }
 
            return result;
        }
 
        /// <summary>
        /// Encodes integers into a byte array. Due to padding if 
        /// encoding integers of less than 8 bits you could reserve 0 
        /// as a special case to allow for detecting surplus results
        /// (or explicitly externally keep track of the expected length
        /// and truncate the excess 0 value integers returned by this 
        /// method).
        /// </summary>
        /// <param name="ints">integer arrays, value of each must be >=0
        ///                    and below the maximum storable in an 
        ///                    unsigned int of bitsPerInt bits</param>
        /// <param name="bitsPerInt"># bits to use to encode each
        ///                          integer</param>
        /// <returns></returns>
        public static byte[] GetBytesFromInts(int[] ints, int bitsPerInt)
        {
            if (bitsPerInt < 1 || bitsPerInt > 31)
            {
                throw new ArgumentOutOfRangeException("bitsPerInt", 
                    "1..31 bit unsigned integers supported only.");
            }
            foreach (var i in ints)
            {
                if (i < 0 || i > (Math.Pow(2, bitsPerInt) - 1))
                {
                    throw new ArgumentOutOfRangeException("ints", 
                                  "Integer is <0 or >((2^bitsPerInt)-1)");
                }
            }
 
            var totalBits = (bitsPerInt * ints.Length);
            var result = new byte[(totalBits / 8) + (totalBits % 8 == 0 ? 0 : 1)];
 
            for (var i = 0; i < ints.Length; i++)
            {
                var startBit = i * bitsPerInt;
                var startByte = (startBit/8);
                var endBit = (startBit + bitsPerInt);
                var endByte = endBit / 8 + (endBit % 8 ==0 ? 0 : 1);
                var byteRange = endByte - startByte;
 
                for (var b = 0; b<byteRange; b++)
                {
                    var bi = startByte + b;
                    Int64 asi64 = ints[i];
                    var shiftedForMerge = b == 0 ?
                (asi64 << (startBit % 8)) :
                (asi64 >> ((b * 8) - (startBit % 8)));
                    result[bi] |= (byte)shiftedForMerge;
                }
            }
 
            return result;
        }
    }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here