🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

How to unpack masked integers from bit-mask?

Started by
9 comments, last by h8CplusplusGuru 4 years, 5 months ago

So I have made this small JS example (see below) where I pack Integers it to bit-mask and the decode back. It kind for works, but I think there must be a more robust and generally faster way (only using bit operators?). Live project to play with https://jsfiddle.net/sa7vkcnd/

var mask = 0;
var lod = 2;
var x = 5;
var y = 8;


mask|=(lod);
mask<<=4;
mask|=(x);
mask<<=10;
mask|=(y);
mask<<=10;


var lodr = mask >> 24;
console.log(lodr);

var xr = mask >> 20;
console.log(xr - (lodr << 4));

var yr = mask >> 10;
console.log(yr - (xr << 10));
Advertisement

Try asking yourself this question:

5+4 = 9

How do I get 5 and 4 from 9 but not an infinity of other pairs of numbers? You don't. 5+4 = 9. you need two of the three numbers to extract the third. That, or other constants and constraints. Your'e sort of barking up a logical fallacy.

Difficult to follow, the way you write it. If you do the shifts on the other side their value remains constant for both pack and unpack. Less error prone and order independent.

Seems you forgot to mask out the other bits on unpack (& 1023). (Edit - seems you use subtractions instead, but that's even more error prone.)

var mask = 0;
var lod = 2;
var x = 5;
var y = 8;

mask |= x;
mask |= y << 10;
mask |= lod << 20;


var xr = mask & 1023;
var yr = (mask >> 10) & 1023;
var lodr = mask >> 20;

… notice you could write this differently:

var yr = (mask >> 10) & 1023;

var yr = (mask >> 10) & 0x3FF;

var yr = (mask >> 10) & 0b1111111111; // 10 bits set, to mask out all the others

var yr = (mask >> 10) & ((1<<10)-1);

The last one does not require to remember numbers with n bits set to one. (Not sure what variants JS supports)

And often it is necessary to mask also the input:

mask |= (y & 1023) << 10;

Becasue if y would be larger than a 10 bit number, this way at least all other packed numbers remain correct.

You are now a master in bit packing :D

@JoeJ I wonder why the last `z` param is decoded incorrect. Do I some-have go over the JS 32 bit int limit? https://jsfiddle.net/wgdphzL1/1/

console.clear();

var mask = 0;
var lod = 3;
var x = 5;
var y = 8;
var z = 5;

mask|=(lod & 3);
mask|=(x & 1023) << 10;
mask|=(y & 1023) << 20;
mask|=(z & 1023) << 30;


var lodr = mask & 3;
var xr = (mask >> 10) & 1023;
var yr = (mask >> 20) & 1023;
var zr = (mask >> 30) & 1023;

console.log(lodr, xr, yr, zr);

Yes, there are only two bits left for z, assuming it uses 32 bit integers. (Can't run on jsfiddle. Also i don't know if all this is guaranteed to work at all with a language that is not type save.)

But the point is, you actually waste 8 bits here:

mask|=(lod & 3); // using only 2 bits

mask|=(x & 1023) << 10; // but then you shift the next number by 10 bits, leaving bit 2-10 unused.

If lod==3 and x==1023 the resulting bits would be 0b111111111100000011, with the zeros showing the unused bits.

Fixing this you get 10bits also for z with a 32bit number and it should work:

mask|=(lod & 3);

mask|=(x & 1023) << 2;

mask|=(y & 1023) << 12;

mask|=(z & 1023) << 22;

There is one more catch to know if you use signed integers for packing. Example in C:

int32_t r = 10, g = 20, b = 80, a = 130;

int32_t packed = r | (g<<8) | (b<<16) | (a<<24);

int32_t ra = packed >> 24; // beacasue the type is signed, the shift op also becomes signed

Here ra would become a negative number with all leading buts set to one, which is not what we want.

This can be avoided either using unsigned types so leading bits become zero, or using the masking we already did before.

@JoeJ Thanks. Understand now it !

Ok, not sure what I was thinking in my earlier post. OK, so you want to pack data in javascript? This is by far the best method, and has many more uses than mere masking:

	<script type="text/javascript">

		window.onload = function(){

			//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays

			//underlying data
			let data = new ArrayBuffer(4);	//size in bytes
	
			//data views
			let vint8 = new Int8Array(data );
			let vint32 = new Int32Array(data );

			const colors = {
				R : 0,
				G : 1,
				B : 2,
				A : 3
			};
			
			vint8[colors.R] = 255;		
			vint8[colors.G] = 0
			vint8[colors.B] = 0
			vint8[colors.A] = 0
			
			//so this is going to be affected by native-endianess	
			console.log( "vint32: " + vint32[0] );
	//vint32[0] == 255
		}

	</script>

The only time you would need to do masks is when the data size in bits is less than one byte.

an more complicated example of masking, you can have an array of say, int64s and write bit fields into the array, crossing int64 boundaries.

Here's an example of writing a 4 bit field into a int8 array, same process applies.


	<script type="text/javascript">

		window.onload = function(){

			console.log( "writing a bit field accross a type boundary" );

			//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays

			const sizeOfByteInBits = 8;

			let buffer = new ArrayBuffer(2);	
			let buffer_vint8 = new Uint8Array( buffer );
			
			//some initial data, will get overwrote if its where the field is going to write
			buffer_vint8[0] = 2;	//left byte
			buffer_vint8[1] = 1;	//right byte

			let dataA = new ArrayBuffer(1);
			let dataA_vint8 = new Uint8Array(dataA);

			//so this is the field, value of 13, 4 bits in size, that we are going to write
			let fieldSizeInBits = 4;
			dataA_vint8[0] = 13;

			//we are going to write dataA accross the buffer byte boundry at a specific bit index
			let destBitIndex = 5; // ( 0-based index)
			
			let data_right = dataA_vint8[0] << destBitIndex;
			
			let bitsLeft =  sizeOfByteInBits - destBitIndex;
			let data_left = dataA_vint8[0] >> bitsLeft;
			
			let clearOffset = fieldSizeInBits;
			buffer_vint8[1] = (( buffer_vint8[1]<< clearOffset ) >> clearOffset ) | data_right;							

			clearOffset -= bitsLeft;
			buffer_vint8[0] = (( buffer_vint8[0] >> clearOffset ) << clearOffset ) | data_left;

			console.log( "left byte: " + buffer_vint8[0] + ", right byte: " + buffer_vint8[1] );
			
		}

	</script>

This topic is closed to new replies.

Advertisement