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#.
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;
}
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 ushort
s, 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);
}
}
}
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?
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();
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
精彩评论