Accumulator
Accumulator

Reputation: 903

How do I combine 4 2-bit values into 1 8-bit value?

This code is what I am using to combine 4 2-bit values (unsigned chars but they only hold values from 0-3) into 1 single unsigned char value

unsigned char nibble = 0;

nibble = (nibble & 0x03) | (output[i] & 0x03);
nibble = (nibble & 0x0C) | (output[i+1] & 0x03) << 2);
nibble = (nibble & 0x30) | (output[i+2] & 0x03) << 4);
nibble = (nibble & 0xC0) | (output[i+3] & 0x03) << 6);

It produces an incorrect value for everything except 00 00 00 00 (it often produces the same result for 2 different sets of 2-bit values).

I'm confused because the code above is an edit of this code, which works fine to combine 2 4-bit values into 1 byte, so why does my version not combine 4 2-bit values into 1 byte?

char byte;
byte = (byte & 0xF0) | (nibble1 & 0xF); // write low quartet
byte = (byte & 0x0F) | ((nibble2 & 0xF) << 4); // write high quartet

I've tried changing 0x03 / 0x0C / 0x30 / 0xC0 to 0xC0 / 0x30 / 0x0C / 0x03, still wrong. Same for changing & 0x03 to & 0xC0.

Upvotes: 0

Views: 1978

Answers (2)

Richard
Richard

Reputation: 61499

It's because you clear bits in the nibble each time.

That is, when you say this:

nibble = (nibble & 0xC0)

what you are really saying is "throw away all the work I've done so far, except for the bits at positions 3 and 4".

This code (untested) will probably solve your problem:

unsigned char nibble = 0;

nibble |= (output[i  ] & 0x03);
nibble |= (output[i+1] & 0x03) << 2;
nibble |= (output[i+2] & 0x03) << 4;
nibble |= (output[i+3] & 0x03) << 6;

If it's really true that output[i+x] only holds values in the range [0,3], then you can change the code as follows:

unsigned char nibble = 0;

assert(0<=output[i  ] && output[i  ]<=3)
nibble |= output[i  ];
assert(0<=output[i+1] && output[i+1]<=3)
nibble |= output[i+1] << 2;
assert(0<=output[i+2] && output[i+2]<=3)
nibble |= output[i+2] << 4;
assert(0<=output[i+3] && output[i+3]<=3)
nibble |= output[i+3] << 6;

The asserts could, of course, be removed if you're really, really sure. But you can also leave them in and have the compiler extirpate them by using the NDEBUG flag (g++ -DNDEBUG mycode.cpp). See this question for further details.

Upvotes: 5

Havenard
Havenard

Reputation: 27934

That's because before populating nibble with the new bits you are filtering it with a mask that if anything does the opposite of what it should do.

nibble = (nibble & 0x03) | (output[i] & 0x03);
nibble = (nibble & 0x0C) | (output[i+1] & 0x03) << 2);
nibble = (nibble & 0x30) | (output[i+2] & 0x03) << 4);
nibble = (nibble & 0xC0) | (output[i+3] & 0x03) << 6);
                   ^^^^ in here you preserve the bits
             you want to replace and zero out everything else

Inverting that mask would work:

nibble = (nibble & ~0x03) | (output[i] & 0x03);
nibble = (nibble & ~0x0C) | (output[i+1] & 0x03) << 2);
nibble = (nibble & ~0x30) | (output[i+2] & 0x03) << 4);
nibble = (nibble & ~0xC0) | (output[i+3] & 0x03) << 6);

However, since nibble already starts as 0 anyway, you don't need that. Just populate the bits as in Richard's answer.

Upvotes: 0

Related Questions