tags:

views:

145

answers:

3

I have an array of four unsigned chars. I want to treat it like a 32-bit number (assume the upper bits of the char are don't care. I only care about the lower 8-bits). Then, I want to circularly shift it by an arbitrary number of places. I've got a few different shift sizes, all determined at compile-time.

E.g.

unsigned char a[4] = {0x81, 0x1, 0x1, 0x2};
circular_left_shift(a, 1);
/* a is now { 0x2, 0x2, 0x2, 0x5 } */

Edit: To everyone wondering why I didn't mention CHAR_BIT != 8, because this is standard C. I didn't specify a platform, so why are you assuming one?

+1  A: 

While keeping in mind plain C the best way is

inline void circular_left_shift(char *chars, short shift) {
    __int32 *dword = (__int32 *)chars;
    *dword = (*dword << shift) | (*dword >> (32 - shift));
}

Uhmm, char is 16 bits long, was not clear for me. I presume int is still 32 bit.

inline void circular_left_shift(char *chars, short shift) {
    int i, part;
    part = chars[0] >> (16 - shift);
    for (i = 0; i < 3; ++i)
        chars[i] = (chars[i] << shift) | (chars[i + 1] >> (16 - shift));
    chars[3] = (chars[3] << shift) | part;
}

Or you could just unwind this cycle.

You could dig further into asm instruction ror, on x86 it's capable of performing such shift up to 31 bits left. Something like a

MOV CL, 31
ROR EAX, CL
Keynslug
I would do this, but CHAR_BIT is 16, so aliasing a 32-bit word on top of a unsigned char[4] doesn't work. I can't rely on non-standard C features, but thanks for your response.
turdfurguson
Just fixed. What is the target machine?
Keynslug
Happens to be a TI DSP where int != 32-bits but I'm not seeing where that would matter in your code anyways. Is this restricted to shifts <= 7 ?
turdfurguson
My bad. @Kevin's solution should do the trick.
Keynslug
A: 

Use union:

typedef union chr_int{
   unsigned int  i;
   unsigned char c[4];
};

It's safer (because of pointer aliasing) and easier to manipulate.

EDIT: you should have mention earlier that your char isn't 8 bits. However, this should do the trick:

#define ORIG_MASK 0x81010102
#define LS_CNT 1

unsigned char a[4] = { 
    ((ORIG_MASK <<  LS_CNT      ) | (ORIG_MASK >> (32 - LS_CNT))) & 0xff,
    ((ORIG_MASK << (LS_CNT +  8)) | (ORIG_MASK >> (24 - LS_CNT))) & 0xff,
    ((ORIG_MASK <<  LS_CNT + 16)) | (ORIG_MASK >> (16 - LS_CNT))) & 0xff,
    ((ORIG_MASK << (LS_CNT + 24)) | (ORIG_MASK >> ( 8 - LS_CNT))) & 0xff
};
ruslik
+1 for use of `unsigned int`, which actually works with test data in the question. Is this independent of endianness on platform?
Steve Townsend
See my comment on the previous answer.
turdfurguson
I see your edit is just an array.. am I missing something?
turdfurguson
Well, this would be OK if the char array is compile time constant too. I thought you asked about that. I see now :)
ruslik
+5  A: 
static void rotate_left(uint8_t *d, uint8_t *s, uint8_t bits)
{
   const uint8_t octetshifts = bits / 8;
   const uint8_t bitshift = bits % 8;
   const uint8_t bitsleft = (8 - bitshift);
   const uint8_t lm = (1 << bitshift) - 1;
   const uint8_t um = ~lm;
   int i;

   for (i = 0; i < 4; i++)
   {
       d[(i + 4 - octetshifts) % 4] =
           ((s[i] << bitshift) & um) | 
           ((s[(i + 1) % 4] >> bitsleft) & lm);
   }
}   

Obviously

Kevin Zhou
This is looking promising, let me run a few testcases. Its much cleaner than my first attempt.
turdfurguson
I see you've assumed little-endian, but it could be easily modified to big endian..
turdfurguson