How to combine two 32-bit integers into one 64-bit integer?

64,510

Solution 1

long long val = (long long) mostSignificantWord << 32 | leastSignificantWord;
printf( "%lli", val );

Solution 2

It might be advantageous to use unsigned integers with explicit sizes in this case:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
  uint32_t leastSignificantWord = 0;
  uint32_t mostSignificantWord = 1;
  uint64_t i = (uint64_t) mostSignificantWord << 32 | leastSignificantWord;
  printf("%" PRIu64 "\n", i);

  return 0;
}
Output

4294967296

Break down of (uint64_t) mostSignificantWord << 32 | leastSignificantWord

  • (typename) does typecasting in C. It changes value data type to typename.

    (uint64_t) 0x00000001 -> 0x0000000000000001

  • << does left shift. In C left shift on unsigned integers performs logical shift.

    0x0000000000000001 << 32 -> 0x0000000100000000

left logical shift

  • | does 'bitwise or' (logical OR on bits of the operands).

    0b0101 | 0b1001 -> 0b1101

Solution 3

There's another way using arrays and pointers:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
    // Two uint32_t to one uint64_t
    uint32_t val1[2] = {1000, 90000};
    uint64_t *val1_u64_ptr = (uint64_t*)val1; //intermediate pointer cast to avoid Wstrict-aliasing warnings
    uint64_t val2 = *val1_u64_ptr;
    printf("val2: %" PRIu64 "\n", val2);
    // val2: 386547056641000


    // back to uint32_t array from uint64_t
    uint64_t val3 = 386547056641000ull;
    uint32_t *val4 = (uint32_t*)&val3;
    printf("val4: %" PRIu32 ", %" PRIu32 "\n", val4[0], val4[1]);
    // val4: 1000, 90000
    
    return 0;
}

This code for me is much easier to understand and read. You are just creating a contiguous space in memory with two 32-bit unsigned int and then this same memory space is read as a single 64-bit unsigned int value and vice-versa. There are no operations involved only memory being read as different types.

EDIT

Forgot to mention that this is great if you already have a 64-bit array read from somewhere then you could easily read everything as 32-bit array pairs:

#include <stdio.h>
#include <inttypes.h>

int main() {
    uint64_t array64[] = {
        386547056641000ull,
        93929935171414ull,
        186655006591110ull,
        73141496240875ull,
        161460097995400ull,
        351282298325439ull,
        97310615654411ull,
        104561732955680ull,
        383587691986172ull,
        386547056641000ull
    };

    int n_items = sizeof(array64) / sizeof(array64[0]);
    
    uint32_t* array32 = (uint32_t*)&array64;
    for (int ii = 0; ii < n_items * 2; ii += 2) {
        printf("[%" PRIu32 ", %" PRIu32 "]\n", array32[ii], array32[ii + 1]);
    }

    return 0;
}

Output:

[1000, 90000]
[3295375190, 21869]
[22874246, 43459]
[2498157291, 17029]
[3687404168, 37592]
[1218152895, 81789]
[3836596235, 22656]
[754134560, 24345]
[4162780412, 89310]
[1000, 90000]

Using union struct

Still better and more readable would be to use a struct union as from https://stackoverflow.com/a/2810339/2548351:

#include <stdio.h>
#include <inttypes.h>

typedef union {
  int64_t big;
  struct {
    int32_t x;
    int32_t y;
  };
} xy_t;

int main() {
    // initialize from 64-bit
    xy_t value = {386547056641000ull};
    printf("[%" PRIu32 ",%" PRIu32 "]\n", value.x, value.y);
    // [1000, 90000]
    
    // initialize as two 32-bit
    xy_t value2 = {.x = 1000, .y = 90000};
    printf("%" PRIu64, value.big);
    // 386547056641000

    return 0;
}

Solution 4

my take:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;

data64 = (unsigned long long) high << 32 | low;

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

Another approach:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;
unsigned char * ptr = (unsigned char *) &data;

memcpy (ptr+0, &low, 4);
memcpy (ptr+4, &high, 4);

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

Both versions work, and they will have similar performance (the compiler will optimize the memcpy away).

The second version does not work with big-endian targets but otoh it takes the guess-work away if the constant 32 should be 32 or 32ull. Something I'm never sure when I see shifts with constants greater than 31.

Solution 5

This code works when both upper32 and lower32 is negative:

data64 = ((LONGLONG)upper32<< 32) | ((LONGLONG)lower32& 0xffffffff);
Share:
64,510

Related videos on Youtube

bei
Author by

bei

Updated on July 09, 2022

Comments

  • bei
    bei almost 2 years

    I have a count register, which is made up of two 32-bit unsigned integers, one for the higher 32 bits of the value (most significant word), and other for the lower 32 bits of the value (least significant word).

    What is the best way in C to combine these two 32-bit unsigned integers and then display as a large number?

    In specific:

    leastSignificantWord = 4294967295; //2^32-1
    
    printf("Counter: %u%u", mostSignificantWord,leastSignificantWord);
    

    This would print fine.

    When the number is incremented to 4294967296, I have it so the leastSignificantWord wipes to 0, and mostSignificantWord (0 initially) is now 1. The whole counter should now read 4294967296, but right now it just reads 10, because I'm just concatenating 1 from mostSignificantWord and 0 from leastSignificantWord.

    How should I make it display 4294967296 instead of 10?

    • Clifford
      Clifford about 14 years
      Your method would only make any sense if you used %8.8X as the format specifier to concatenate the hex values of the words. For it to work in decimal, the maximum integer value would have to be (10^n)-1 where n were the number of decimal digits, and that will never be the case on a binary machine!
  • Alex Korban
    Alex Korban about 14 years
    << 32 will not work right if mostSignificantWord is a 32-bit integer. It needs to be cast to a 64-bit type first. By the way, in g++ long and long long are both 8 bytes.
  • caf
    caf about 14 years
    It is irrelevant whether the shift amount constant is 32 or 32ULL, since they both have the same value and that is all that's used for the shift. As long as the value being shifted has the type unsigned long long, everything is hunky dory.
  • Adrian McCarthy
    Adrian McCarthy about 14 years
    Why even show the non-portable solution? Now somebody will copy it, and later the code will be moved to another application, and something will eventually break and it'll take forever to debug.
  • Steve Jessop
    Steve Jessop about 14 years
    @Alex: in g++ on x64, long is 64 bits. On x86 it is 32 bits.
  • Norman Ramsey
    Norman Ramsey about 14 years
    Please use C99 types uint32_t and uint64_t. This is why they are there.
  • Norman Ramsey
    Norman Ramsey about 14 years
    Use the C99 types with explicit sizes. It is why they are there.
  • Norman Ramsey
    Norman Ramsey about 14 years
    Brilliant. Someone who not only knows how to use the C99 types with explicit sizes, but who also understands how to use the related printf macros. +10 if I could. SO-ers, unite! This answer deserves top billing!
  • bei
    bei almost 14 years
    Could anybody break down what this line means? uint64_t i = (uint64_t) mostSignificantWord << 32 | leastSignificantWord; And what's the PRIu64? My compiler doesn't seem to like it, says "expected ) after PRIu64.
  • jfs
    jfs almost 14 years
    @Bei337: inttypes.h is a part of C99 standard. It defines format specifiers for printf/scanf such as PRIu64 and it includes stdint.h that defines typedefs such as uint32_t. If you are using MSVC++/Windows then take inttypes.h from code.google.com/p/msinttypes
  • bei
    bei almost 14 years
    Great explanation, thank you! My compiler didn't like the macro, and I didn't want to include the header, so I used twk's implementation instead. But I learned the most from your post, thanks Sebastian.
  • jfs
    jfs almost 14 years
    @Bei337: It might help if you compile the code as C code (not as C++). C++ requires __STDC_FORMAT_MACROS to be defined to use PRI.. macros.
  • Premal Shah
    Premal Shah almost 12 years
    how do u reverse the process to return the 32 bit integers from the 64 bit integer?
  • jfs
    jfs over 11 years
    @PremalShah: uint32_t lo = (uint32_t)n, hi = (n >> 32);, where n is a unit64_t variable.
  • dove
    dove over 11 years
    could you elaborate and explain your answer
  • mohit
    mohit over 11 years
    this will only work on little-endian machines.one more assumption is unsigned long is 64 bits and unsigned int is 32 bits(can use uint32 and uint64). now the explanation data64=lowerword this will copy the lowerword to the lower bytes of data64. (unsigned int)data64 convert it to 32bits. &((unsigned int)data64) gives me the address of type unsigned int adding 1 will incrrement the address to the higher bytes of data64 finally * of this stores the upperword in those higher bytes.
  • underscore_d
    underscore_d over 5 years
    The inputs are unsigned, so in the absence of the OP saying otherwise, shouldn't the output be too? long long on its own implies signed long long int.
  • Peter Cordes
    Peter Cordes almost 5 years
    This has undefined behaviour; it violates strict aliasing. Use memcpy if you want to do this, or better don't do it at all. Compilers understand ((uint64_t)a<<32) | b
  • noname2019
    noname2019 over 3 years
    if included -Wall or -Werror -> result (error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]). Your code for gcc 8.3: uint64_t val2 = ((uint64_t)val1); only without -Wall and -Werror :(
  • caiohamamura
    caiohamamura over 3 years
    Yes, strict-aliasing is expected to throw warnings, this is where you must tell the compiler you know what you are doing when you cast pointers to different types. Just be bold and decorate this piece of code with: #pragma GCC diagnostic push and #pragma GCC diagnostic ignored "-Wstrict-aliasing" before the code and #pragma GCC diagnostic pop after the code.
  • caiohamamura
    caiohamamura over 3 years
    Another way would be to derefence in two steps, instead of uint64_t val2 = *(uint64_t*)val1 you would cast the pointer types uint64_t *val2 = (uint64_t*)val1 and then dereference that pointer uint64_t result = *val2, I tried it here and everything went ok.
  • Andrew Henle
    Andrew Henle almost 3 years
    Don't ever do this. When you write code per this answer, you're just hoping your compiler doesn't do this to you: C undefined behavior. Strict aliasing rule, or incorrect alignment?. I personally prefer code that doesn't rely on "Gee, I hope this works!!" like the code posted in this answer does. The best anyone can say about code like that posted in this answer is, "I haven't observed it to fail. Yet." Write better code than this.
  • caiohamamura
    caiohamamura almost 3 years
    Well, I don't totally agree with that. If you ever say that to anyone then it is the same as stating that anyone except experts can write any code and you would restrict anybody to actually learn anything or even use their creativity to think outside the box and create new solutions. I just had in my mind that instead of bitshifting one uint32_t leftwise and concatenating (OR) the other one to the right would be the same as putting two contiguos uint32_t in memory and reading them as uint64_t. Shouldn't I test it? I'm no expert at all but I do not like to think inside the box either.