Search code examples
cunsignedsignedstdint

Storing int16_t's in uint64_t's


So I've got 4 uint16_t's, and I am trying to store them in a single uint64_t. I think I have that working.

uint64_t encode(int16_t A, int16_t B, int16_t C, int16_t D){
    return (uint64_t)((uint16_t) A) << 48
         | (uint64_t)((uint16_t) B) << 32
         | (uint64_t)((uint16_t) C) << 16
         | (uint64_t)((uint16_t) D) <<  0;
}

int main(){

    int16_t A1 = -32000;
    int16_t B1 = -31000;
    int16_t C1 = 16004;
    int16_t D1 = 16007;

    uint16_t A2 = 32000;
    uint16_t B2 = 15000;
    uint16_t C2 = -4;
    uint16_t D2 = -7;

    uint64_t E = encode(A1, B1, C1, D1)
               + encode(A2, B2, C2, D2);

    printf("%d\n", (int16_t)((E >> 48)));
    printf("%d\n", (int16_t)((E >> 32)));
    printf("%d\n", (int16_t)((E >> 16)));
    printf("%d\n", (int16_t)((E >>  0)));
}

I have been able to encode 4 int16_t's, and then get them back using this method. Now, I would like to add together two of the encoded forms, decode them, and get back to the original operation. I seem to be getting a bunch of off by 1 errors.

My output here is 0, -15999, 16001, 16000. Two outputs are correct (0 and 16000) and two are off by one.

I'll note that I have tried to first convert int16_t to uint16_t (add 32768), with no luck.


Solution

  • You're getting carry from one number to the next. Exactly the same as if you encoded the decimal numbers 1 and 9 as 19, 2 and 3 as 23 and then added them together: 19 + 23 = 42, but 1 + 2 = 3. So the top digit is 'off by one'.

    Even if you want to avoid full unpacking, you could still do something like:

    result = 
      ((a&0xffff0000ffff0000 + b&0xffff0000ffff0000) & 0xffff0000ffff0000) |
       (a&0x0000ffff0000ffff + b&0x0000ffff0000ffff) & 0x0000ffff0000ffff))
    

    ... which acts in two steps to avoid inappropriate carry.