Search code examples
cmathbit-manipulationpackinguint64

C Struggling with packing unsigned ints into uint64_t


I am given a collection of data and I am required to pack it into a uint64_t value, which in the following example takes the form of type "weatherlog_t"

I am not allowed to use arithmetic operators (+, ++, -, --, *, %, /, ...), I am allowed however to use bitwise operators (&, |, ^, <<, >>, ~) and logical operators (!, =, --, !=, &&, and ||)

I do however have predefined add() and sub() functions that handle bitwise addition and subtraction, and they are used in the following example. These have been tested and I'm fairly sure they work to the degree needed here.

According to the instructions, the 64-bit value must be arranged as follows:

    /* - year :: 6 bits -- stored as the number of years since the year 2000.
    - month :: 4 bits
    - day :: 5 bits
    - zip_code :: 16 bits
    - high_temp :: in degrees Fahrenheit, stored as an 8-bit signed integer
    - low_temp :: in degrees Fahrenheit, stored as 8-bit signed integer
    - precipitation :: in mm. stored as a 10-bit unsigned integer.
    - average_wind_speed :: 7 bits. unsigned int km/hr.

    All of these are packed into a 64 bit unsigned integer in the above order.

    We'd store:
- year :: 2015, which is 15 years from 2000, so 001111
- month :: September, which is the 9th month, so 1001.
- day :: 16, which is 1 0000
- zip_code :: 19122 which is 0100 1010 1011 0010
- high_temp :: 85F, so 0101 0101
- low_temp :: 65F, so 0100 0001
- precipitation :: 35 mm so 00 0010 0011
- average wind speed :: 5 km/h, so 000 0101

And all would be packed into a single 64-bit unsigned integer:

00 1111 1001 10000 0100 1010 1011 0010 0101 0101 0100 0001 00 0010 0011 000 0101

OR

0011 1110 0110 0000 1001 0101 0110 0100 1010 1010 1000 0010 0001 0001 1000 0101 */

So far, what I have is:

weatherlog_t pack_log_entry(unsigned int year, unsigned int month, unsigned int day,
                        unsigned int zip, int high_temp, int low_temp,
                        unsigned int precip, unsigned int avg_wind_speed) {


weatherlog_t ret = 0;

unsigned int newYear = sub(year, 2000);

ret = (ret << 6);
ret = add(ret, newYear);

ret = (ret << 4);
ret = add(ret, month);

ret = (ret << 5);
ret = add(ret, day);

ret = (ret << 16);
ret = add(ret, zip);

ret = (ret << 8);
ret = add(ret, high_temp);

ret = (ret << 8);
ret = add(ret, low_temp);

ret = (ret << 10);
ret = add(ret, precip);

ret = (ret << 6);
ret = add(ret, avg_wind_speed);


return ret;
}

However, when I go in and test this, checking the binary value of ret, it seems to stop at 32-bits, and shifting left after this point causes any bits left of the 32nd leftmost bit to be lost. I am struggling to understand what I am doing wrong, although I am new at bitwise arithmetic and do not fully understand as of yet how it interacts with the C language.

EDIT: As requested, code for add() and subtract()

unsigned int add(unsigned int i, unsigned int j) {
/* can be done in a total of 7 lines, including one to declare an unsigned int, */
/* two for a while loop, and one for the return
 You're not required to do it in 7 lines though . */
while(j != 0){
    unsigned int carry = i & j;

    i = i ^ j;

    j = carry << 1;
}

return i;
}


unsigned int sub(unsigned int i, unsigned int j) {
/* Similar 7 lines, although there is a shorter way */
while (j != 0){
    unsigned int borrow = (~i) & j;

    i = i ^ j;

    j = borrow << 1;
}

return i;
}

Solution

  • I have no idea what you need those add/sub functions for; seems like obfuscation. Packing data in specific bits is much more straight-forward:

    #define YEAR_POS  58
    #define MONTH_POS 48
    
    ret = (uint64_t)year  << YEAR_POS  |
          (uint64_t)month << MONTH_POS |
           ...
    

    This have benefits of being 1) fast, and 2) endian-independent = fully portable.

    You might have to mask each variable in advance if you suspect that they contain garbage beyond the specified sizes:

    #define YEAR_SIZE 6
    year &= (1u << YEAR_SIZE)-1;