I am trying to use bitwise operators to convert four 16-bit integers into a single 64-bit integer in C (OS is Linux running Ubuntu 20.04 Server). My overall goal is to be able to do this with four 32-bit integers into a single 128-bit integer (an IPv6 source IP address from the four 32-bit integers inside the in6_addr
struct). However, in my example below, I am using four 16-bit integers and a single 64-bit integer since I've had issues assigning 128-bit integers using GCC (I'd assume I can apply the same logic from below to my overall goal).
My system's byte order is also in little endian in this case. However, if you'd like to show how to do this on big endian as well, feel free to do so!
Here's my current testing code in C:
#include <stdio.h>
#include <inttypes.h>
int main()
{
uint16_t nums[4];
uint64_t num = 2310051230312123321;
uint64_t mainnum = 0;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
nums[0] = num >> 0; // xxxx ---- ---- ----
nums[1] = num >> 16; // ---- ---- ---- xxxx
nums[2] = num >> 32; // ---- ---- xxxx ----
nums[3] = num >> 48; // ---- xxxx ---- ----
#else
/*
nums[0] = num << 0;
nums[1] = num << 16;
nums[2] = num << 32;
nums[3] = num << 48;
*/
#endif
printf("Num => %" PRIu64 "\nNum #1 => %" PRIu16 "\nNum #2 => %" PRIu16 "\nNum #3 => %" PRIu16 "\nNum #4 => %" PRIu16 "\n\n", num, nums[0], nums[1], nums[2], nums[3]);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
mainnum = ((uint64_t)(nums[0] >> 16 | nums[1]) >> 48) | (nums[3] << 16 | nums[2]);
#else
/* ... */
#endif
uint16_t other[4];
other[0] = mainnum >> 0;
other[1] = mainnum >> 16;
other[2] = mainnum >> 32;
other[3] = mainnum >> 48;
printf("Num => %" PRIu64 "\nNum #1 => %" PRIu16 "\nNum #2 => %" PRIu16 "\nNum #3 => %" PRIu16 "\nNum #4 => %" PRIu16 "\n", mainnum, other[0], other[1], other[2], other[3]);
return 0;
}
The program outputs the following:
Num => 2310051230312123321
Num #1 => 19385
Num #2 => 54197
Num #3 => 62298
Num #4 => 8206
Num => 537850714
Num #1 => 62298
Num #2 => 8206
Num #3 => 0
Num #4 => 0
I want num
and mainnum
to be the same.
I know I am not understanding something here and I don't have much experience with bitwise operators.
mainnum = ((uint64_t)(nums[0] >> 16 | nums[1]) >> 48) | (nums[3] << 16 | nums[2]);
To my understanding, I am starting at nums[0]
and I'd assume this should populate the first 16 bits with this value. From here, I am shifting to the right by 16-bits to put us at the 48 bits offset and replace the 16 bits after that with nums[1]
(in which, I believe is the most significant on little endian).
From here, I then shift mainnum
to the right by 48 bits to put us at the 16 bit offset. I then replace the next 16 bits with nums[3]
and shift to the left by 16 bits and replace the rest with nums[2]
.
I definitely need more practice with bitwise operators (it is on my to-do list) and I'm sure I'm doing something silly here. I just wanted to see if anybody could correct me on what I'm doing wrong.
Any help is highly appreciated and thank you for your time!
You need the same/corresponding indexes and shift amounts that you used to create nums
from num
:
nums[0] = num >> 0; // xxxx ---- ---- ----
nums[1] = num >> 16; // ---- ---- ---- xxxx
nums[2] = num >> 32; // ---- ---- xxxx ----
nums[3] = num >> 48; // ---- xxxx ---- ----
And, each term needs a (uint64_t)
cast to force promotion to 64 bits. Otherwise, the shift will exceed the size used for intermediate terms on the right side of the assignment (e.g. they'll be done with int
and/or unsigned int
).
mainnum |= (uint64_t) nums[0] << 0;
mainnum |= (uint64_t) nums[1] << 16;
mainnum |= (uint64_t) nums[2] << 32;
mainnum |= (uint64_t) nums[3] << 48;
I prefer the above because it's cleaner/clearer and [when optimized] will produce the same code as doing it with a single statement:
mainnum =
(uint64_t) nums[0] << 0 |
(uint64_t) nums[1] << 16 |
(uint64_t) nums[2] << 32 |
(uint64_t) nums[3] << 48;