Skip to main content
gamedev.net
🔒 Locked

Packing bits into bytes.

Started by Arkasian 13 replies 2.7k views
Original Post
Arkasian
Arkasian
Hi folks. I'm wondering if you could help me with a problem I'm having. I'm fiddling around with some GBA programming and I'm having problems with packing bits into bytes. I need to do the following: I have an 8-bit number which has a 4-bit number (0..15) packed into it. I need to extract these 4-bit numbers and then pack 2 of them into an 8-bit unsigned char. If anyone could help out with a handy macro or function, I'd love you forever. This bit manipulation stuff is doing my head in! Thanks P.S. Would this be done differently on Big Endian and Little endian processors?
TDragon
TDragon
Quote:
Original post by Arkasian
I have an 8-bit number which has a 4-bit number (0..15) packed into it.
What does that mean? Does it have two 4-bit numbers packed into it, or is it just no more than 4 bits used? We need some clarification.

Quote:
P.S. Would this be done differently on Big Endian and Little endian processors?
Yes, it would.

Arkasian
Arkasian
For instance:

unsigned char x = 10; // Number will always be in range of 0..15
unsigned char y = 11;

// I now need to squeeze both x an y into this value.
unsigned char packed;
CTar
CTar
Assuming you are using C or C++ you could do like this:

unsigned char Pack(unsigned char LowPart,unsigned char HighPart){    // Use 15 (1111 in binary) to ensure the part isn't higher    // than 15.    // Use << 4 to move all bits 4 places to the side    return (LowPart&15) + ((HighPart&15)<<4);}unsigned char ExtractLowPart(unsigned char PackedChar){    // Extract lower 4 bits    return (PackedChar&15);}unsigned char ExtractHighPart(unsigned char PackedChar){    // Extract the 4 high bits    return (PackedChar>>4);}


This should work.

[Edited by - CTar on October 26, 2005 10:43:58 AM]
arm
arm
You could always use bitfields, assuming c/c++ again:

struct double_nibble{  unsigned char x : 4;    unsigned char y : 4;  };void my_function(){  double_nibble dn;  // Make sure that the size of the struct is actually a byte  assert(sizeof(double_nibble) == 1);  dn.x = 11;  dn.y = 10;}


The one problem I have with this method is making sure that the compiler gives me the right size data structure (hence the assert), sometimes they tend to pad structures out to 2/4/8 bytes etc.

EDIT: Note, this method is not endian proof - you'd need to flip the values in that case.
Scet
Scet
The GBA only uses Little Endian. The normal ARM7 uses both, but Nintendo is using a cheaper cut-down version.
CTar
CTar
Quote:
Original post by TDragon
CTar -- don't forget your bit shift for extracting the high part.


No because I &(and) with 240 (11110000 in binary), this should provide slightly better performance (not that I tried to make these functions efficient then I could have made function too which assumed the char only was 4 bit and didn't bother and'ing etc.), but you could of course just do this:

unsigned char ExtractHighPart(unsigned char PackedChar){    return (PackedChar>>4)&15;}

Arkasian
Arkasian
Wow! Thanks a lot folks.

I'm still a little confused by all the bit manipulation operators, but I'm getting there. I've just tried out one of the code snippets and it works a charm.

cheers!
CTar
CTar
Quote:
Original post by TDragon
CTar: If you don't use the bit shift, the value will be an 8-bit number 24 greater than the original number packed in with your Pack function.


Yeah sorry forgot that, thanks. I'm editting my original post now.
BeerNutts
BeerNutts
Quote:
Original post by TDragon
Quote:
P.S. Would this be done differently on Big Endian and Little endian processors?
Yes, it would.


No it wouldn't matter a bit if you're only using 8-bit values.

If you were using 16 or 32-bit values, then the endianess comes into play (because byte 0 would be swapped for the different endianess's on 16 or 32-bit bit), but the bit positions are always the same within a byte.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)
Aldacron
Aldacron
Quote:
Original post by arm
The one problem I have with this method is making sure that the compiler gives me the right size data structure (hence the assert), sometimes they tend to pad structures out to 2/4/8 bytes etc.


Most compilers that pad structs will always pad them - not sometimes. You can usally specify the byte alingment boundaries for all structs in the app with a command line switch, or for individual structs with a pragma in some compilers or a special attribute keyword in other compilers. Look in your compiler's documentation and see which it uses and get rid of that assert!

Quote:
EDIT: Note, this method is not endian proof - you'd need to flip the values in that case.


Actually, no. When using a bitfield the member values will always be referenced the same: mybitfield.x and mybitfield.y will always return the appropriate value. Endianess has no influence on bitfields. In fact, the ordering of bitfields is undefined in the C spec and is implementation dependent. The compiler may pack the fields from hi to lo, or from lo to hi, in the order you declare them, in reverse order, or in some mixed order, no matter the endianess of the system. All you need do is reference the bitfield members by name and it will work. You will most likely never need to worry about the ordering of the bitfield.
arm
arm
Quote:
Most compilers that pad structs will always pad them - not sometimes. You can usally specify the byte alingment boundaries for all structs in the app with a command line switch, or for individual structs with a pragma in some compilers or a special attribute keyword in other compilers. Look in your compiler's documentation and see which it uses and get rid of that assert!


When I use the word 'sometimes' I mean that some compilers pad differently to others, which could cause problems when porting code. Also, all of those #pragma's are compiler-specific which is really going to give you a hard time if you are porting to a different platform (with different endianess) which doesn't have your specific brand of compiler. I think I'll keep that assert for now! At least its going to be supported over different compilers, AND, only rears its ugly head during debug.

Quote:
Actually, no. When using a bitfield the member values will always be referenced the same: mybitfield.x and mybitfield.y will always return the appropriate value. Endianess has no influence on bitfields. In fact, the ordering of bitfields is undefined in the C spec and is implementation dependent. The compiler may pack the fields from hi to lo, or from lo to hi, in the order you declare them, in reverse order, or in some mixed order, no matter the endianess of the system. All you need do is reference the bitfield members by name and it will work. You will most likely never need to worry about the ordering of the bitfield.


The fact that the C-spec is so loosely defined for bitfields would just make the problems of endianess even worse in this example. Imagine you write a binary file on a big-endian machine then read it in on a small-endian machine, using a different brand of compiler for each machine. Now there's absolutely no garantee of the fields matching up!

It is for this reason that I can not recommend my bitfield method for cross-platform/compiler portable code. Keep bitfields for internal and/or same-platform use.

arm.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.