0

I have an array of ushorts, with each ushort representing a 12-bit word. This needs to be tightly packed into an array of bytes. It should look like this in the end:

|  word1    |  word2    |  word3    |  word4   |
| byte1 | byte2 | byte3 | byte4 | byte5 | byte6|

Since each word only uses 12 bits, 2 words will be packed into 3 bytes.

Could someone help? I'm a bit stuck on how to do this in C#.

3
  • 2
    I assume that you mean "Since each word only uses 12 bits [...]"? Commented Jan 15, 2011 at 14:03
  • What will happen if you have 2n+1 words? Commented Jan 15, 2011 at 14:57
  • The last byte in the array would only be half full. Commented Jan 15, 2011 at 20:06

5 Answers 5

2

You're probably going to have to brute-force it.

I'm not a C# guy, but you are looking at something along the lines of (in C):

unsigned incursor, outcursor;
unsigned inlen = length(inputarray);  // not literally
for(incursor=0,outcursor=0;incursor < inlen; incursor+=2,outcursor+=3{
   outputarray[outcursor+0] = ((inputarray[incursor+0]) >> 4) & 0xFF;
   outputarray[outcursor+1] = ((inputarray[incursor+0] & 0x0F)<<4 | ((inputarray[incursor+1]>>8) & 0x0F);
   outputarray[outcursor+2] = inputarray[incursor+1] & 0xFF;
}
Sign up to request clarification or add additional context in comments.

2 Comments

Hm. cannot see, why it is greater then using the .NET way (via binary streams), but ...
I need them to be tightly packed in the array of bytes, I think the only way to do this is with bit shifting. (Or is there a .NET way?) I need it packed this way because I need to write it this way to a .hex file.
2

If you want to use the array as an array of UInt16 while in-memory, and then convert it to a packed byte array for storage, then you'll want a function to do one-shot conversion of the two array types.

public byte[] PackUInt12(ushort[] input)
{
    byte[] result = new byte[(input.Length * 3 + 1) / 2]; // the +1 leaves space if we have an odd number of UInt12s. It's the unused half byte at the end of the array.
    for(int i = 0; i < input.Length / 2; i++)
    {
        result[i * 3 + 0] = (byte)input[i * 2 + 0];
        result[i * 3 + 1] = (byte)(input[i * 2 + 0] >> 8 | input[i * 2 + 1] << 4);
        result[i * 3 + 2] = (byte)(input[i * 2 + 1] >> 4);
    }
    if(input.Length % 2 == 1)
    {
        result[i * 3 + 0] = (byte)input[i * 2 + 0];
        result[i * 3 + 1] = (byte)(input[i * 2 + 0] >> 8);
    }
    return result;
}

public ushort[] UnpackUInt12(byte[] input)
{
    ushort[] result = new ushort[input.Length * 2 / 3];
    for(int i = 0; i < input.Length / 3; i++)
    {
        result[i * 2 + 0] = (ushort)(((ushort)input[i * 3 + 1]) << 8 & 0x0F00 | input[i * 3 + 0]);
        result[i * 2 + 1] = (ushort)(((ushort)input[i * 3 + 1]) << 4 | input[i * 3 + 1] >> 4;)
    }
    if(result.Length % 2 == 1)
    {
        result[i * 2 + 0] = (ushort)(((ushort)input[i * 3 + 1]) << 8 & 0x0F00 | input[i * 3 + 0]);
    }
    return result;
}

If, however, you want to be efficient about memory usage while the application is running, and access this packed array as an array, then you'll want to have a class that returns ushorts, but stores them in byte[].

public class UInt12Array
{
    // TODO: Constructors, etc. 

    private byte[] storage;

    public ushort this[int index] 
    {
        get
        {
            // TODO: throw exceptions if the index is off the array.
            int i = index * 2 / 3;
            if(index % 2 == 0)
                return (ushort)(((ushort)storage[i * 3 + 1]) << 8 & 0x0F00 | storage[i * 3 + 0]);
            else
                return (ushort)(((ushort)storage[i * 3 + 1]) << 4 | storage[i * 3 + 1] >> 4;)
        }
        set
        {
            // TODO: throw exceptions if the index is off the array.
            int i = index * 2 / 3;
            if(index % 2 == 0)
                storage[i * 3 + 0] = (byte)value;
                storage[i * 3 + 1] = (byte)(value >> 8 | storage[i * 3 + 1] & 0xF0);
            else
                storage[i * 3 + 1] = (byte)(storage[i * 3 + 1] & 0x0F | value << 4);
                storage[i * 3 + 2] = (byte)(value >> 4);

        }
    }
}

Comments

1

Why not store the 12-bit words in a byte array and provide a getter and a setter method that read and write the ushort's byte to the correct index in the array?

Comments

1

Trying to solve this with LINQ was fun!

Warning: For entertainment purposes only - do not use the below performance abominations in real code!

First try - group pairs of uints, create three bytes out of each pair, flatten list:

byte[] packedNumbers = (from i in Enumerable.Range(0, unpackedNumbers.Length)
                        group unpackedNumbers[i] by i - (i % 2) into pairs
                        let n1 = pairs.First()
                        let n2 = pairs.Skip(1).First()
                        let b1 = (byte)(n1 >> 4)
                        let b2 = (byte)(((n1 & 0xF) << 4) | (n2 & 0xF00) >> 8)
                        let b3 = (byte)(n2 & 0xFFFF)
                        select new[] { b1, b2, b3 })
                        .SelectMany(b => b).ToArray();

Or slightly more compact, but less readable:

byte[] packedNumbers = unpackedNumbers
  .Select((Value, Index) => new { Value, Index })
  .GroupBy(number => number.Index - (number.Index % 2))
  .SelectMany(pair => new byte[] {
    (byte)(pair.First().Value >> 4),
    (byte)(((pair.First().Value & 0xF) << 4) | (pair.Skip(1).First().Value & 0xF00) >> 8),
    (byte)(pair.Skip(1).First().Value & 0xFFFF) }).ToArray();

Strings anyone?

char[] hexChars = unpackedNumbers.SelectMany(n => n.ToString("X4").Substring(1, 3)).ToArray();
byte[] packedNumbers = (from i in Enumerable.Range(0, hexChars.Length / 2)
                        select byte.Parse(hexChars[i * 2].ToString() + hexChars[i * 2 + 1], NumberStyles.HexNumber))
                       .ToArray();

Comments

0

According to the comments given, I suppose, the current answers is preferable.

But about this should do it also:

public byte[] ushort2byteArr(ushort[] arr) {
  System.IO.MemoryStream ms = new System.IO.MemoryStream(); 
  System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms);
  for (int i = 0; i < arr.Length-1;) { // check upper limit! 
      // following is wrong! must extend this to pack 8 12 bit words into 3 uint32! 
      UInt32 tmp = arr[i++] | (arr[i++] << 12) ... ; 
      bw.Write(tmp);
  }
  return ms.ToArray(); 
}

its not tested. take it as pseudocode to get the clue. especially the word -> uint32 conversion. May need some padding at the end?

@edit: made a function out of it for better clearance

3 Comments

This doesn't provide any packing. You're taking two 12-bit numbers and storing them in a 32-bit. 24 in 32 is exactly as packed as the original 12 in 16. You'd need to write 12, 24, or 36 bits at a time to the stream, not 16 or 32, to get the packing. Doing the bit shifting and byte manipulation in the other answers is the only way to do that.
In response to your comment on the other answer: Doing it directly with arrays doesn't create two relatively heavy stream objects, which would need to be garbage collected. Also, the MemoryStream is probably going to need to reallocate it's internal storage array several times, hitting the garbage collector more, and resulting in several bulk copies of bytes, making it less efficient.
@David: A MemoryStream is just a byte array conveniently wrapped up as a stream. You can avoid resizing by specifying its initial capacity in its constructor. There's nothing 'heavy' about it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.