1

I am working with Javascript typed arrays, and I need to compress them as much as possible for networking purposes.

The smallest built in array Javascript has is 8 bits per entry. This will store numbers between 0 and 255.

However the data I'm working with will only contain numbers between 0 and 3. This can can be stored using 2 bits.

So my question is, if I have an 8 bit array that is populated with data only using numbers between 0 and 3, how can I "convert" it into a 2 bit array?

I know I'll need to use a bit operator, but I'm not sure how to make a mask that will only focus on 2 bits at a time.

5
  • You should look at & and >> Commented Jun 20, 2018 at 1:24
  • An example, selecting the first four bits - using binary representations to emphasize what's happening: ((Number.parseInt("10100110", 2) & Number.parseInt("11110000", 2)) >> 4).toString(2); ("10100110" is our data, "11110000" is our bitmask) Commented Jun 20, 2018 at 1:31
  • @ASDFGerte I get what you're saying about using the & operator as a bitmask. However I'm a little confused how to use that through the entire array. Would I have to make a loop where each iteration of that loop updates the bitmask to the current 2 bits I want to target? Commented Jun 20, 2018 at 1:35
  • For writing the data, you can just use |, e.g. consider two 2-Bit data points have been written already (e.g. 11 and 10), and the data currently looks like this: 00001011 (binary), just use let x = Number.parseInt("00001011", 2); x |= (Number.parseInt("10", 2) << 4); (where 10 is the example data we want to add). As there are only 4 positions per 8-Bit, i'd not write a loop but just write it all explicitly. Commented Jun 20, 2018 at 1:39
  • @ASDFGerte Ahhh I understand now! So basically bit shift the data you want to store the appropriate amount, and then use the | operator to combine it with the existing data. Thank you! Commented Jun 20, 2018 at 1:46

2 Answers 2

1

A longer example is hard to fit into a comment :)

Up front, please note that very often, network data is compressed already - e.g. with gzip (especially when there is concern about data volume and the network libraries are setup properly). However, this is not always the case and would still not be as compact as doing it manually.

You need to keep track of two things, the current array index and the current slot inside the 8-Bit that is being read or written. For writing, | is useful, for reading &. Shifts (<< or >>) are used to select the position.

const randomTwoBitData = () => Math.floor(Math.random() * 4);

//Array of random 2-Bit data
const sampleData = Array(256).fill().map(e => randomTwoBitData());

//four entries per 8-Bit
let buffer = new Uint8Array(sampleData.length / 4);

//Writing data, i made my life easy
//because the data is divisible by four and fits perfectly.
for (let i = 0; i < sampleData.length; i += 4) {
  buffer[i / 4] =
    sampleData[i] |
    (sampleData[i + 1] << 2) |
    (sampleData[i + 2] << 4) |
    (sampleData[i + 3] << 6);
}

//padding for console logging
const pad = (s, n) => "0".repeat(Math.max(0, n - s.length)) + s;

//Some output to see results at the middle
console.log(`buffer: ${pad(buffer[31].toString(2), 8)}, ` +
`original data: ${pad(sampleData[127].toString(2), 2)}, ` +
`${pad(sampleData[126].toString(2), 2)}, ` +
`${pad(sampleData[125].toString(2), 2)}, ` +
`${pad(sampleData[124].toString(2), 2)}`);

console.log("(order of original data inverted for readability)");
console.log("");

//Reading back:
let readData = [];
buffer.forEach(byte => {
  readData.push(byte & 3);           // 3   is 00000011 binary
  readData.push((byte & 12) >> 2);   // 12  is 00001100 binary
  readData.push((byte & 48) >> 4);   // 48  is 00110000 binary
  readData.push((byte & 192) >> 6);  // 192 is 11000000 binary
});

//Check if data read from compacted buffer is the same
//as the original
console.log(`original data and re-read data are identical: ` +
readData.every((e, i) => e === sampleData[i]));

Sign up to request clarification or add additional context in comments.

Comments

1

Here is a function to do 8 bits number to 2 bits array of length 4 with & and >>:

function convert8to2(val){
    var arr = [];
    arr.push((val&parseInt('11000000', 2))>>6);
    arr.push((val&parseInt('00110000', 2))>>4);
    arr.push((val&parseInt('00001100', 2))>>2);
    arr.push((val&parseInt('00000011', 2)));
    return arr;
}
function convert2to8(arr){
    if(arr.length != 4)
      throw 'erorr';
    return (arr[0]<<6)+(arr[1]<<4)+(arr[2]<<2)+arr[3];
}
// 228 = 11100100
var arr = convert8to2(228);
console.log(arr);
console.log(convert2to8(arr));

Edited

Change the example value and format the binary number with leading 0

Edited

Add convert2to8 and create an example usage:

function convert8to2(val){
    var arr = [];
    arr.push((val&parseInt('11000000', 2))>>6);
    arr.push((val&parseInt('00110000', 2))>>4);
    arr.push((val&parseInt('00001100', 2))>>2);
    arr.push((val&parseInt('00000011', 2)));
    return arr;
}
function convert2to8(arr){
    if(arr.length != 4)
      throw 'erorr';
    return (arr[0]<<6)+(arr[1]<<4)+(arr[2]<<2)+arr[3];
}

var randomData = [];
for(var i=0;i<10;i++){
  randomData.push(Math.floor(Math.random() * 255));
}

console.log(randomData);

var arrayOf2 = []
for(var i=0;i<randomData.length;i++){
    arrayOf2.push(convert8to2(randomData[i]));
}
console.log(arrayOf2);

var arrayOf8 = [];
for(var i=0;i<arrayOf2.length;i++){
    arrayOf8.push(convert2to8(arrayOf2[i]));
}
console.log(arrayOf8);

1 Comment

wow that is awesome thank you! But how would I convert this to a "convert2to8" function?

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.