31

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?

Joe
  • 41,484
  • 20
  • 104
  • 125
bei
  • 933
  • 3
  • 9
  • 17
  • 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! – Clifford May 04 '10 at 21:34

8 Answers8

78

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

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 5
    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! – Norman Ramsey May 05 '10 at 00:11
  • 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. – bei May 05 '10 at 15:59
  • 1
    @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 http://code.google.com/p/msinttypes/ – jfs May 05 '10 at 16:33
  • 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. – bei May 05 '10 at 19:02
  • 1
    @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. – jfs May 05 '10 at 20:52
  • how do u reverse the process to return the 32 bit integers from the 64 bit integer? – Premal Shah Jun 08 '12 at 21:28
  • 1
    @PremalShah: `uint32_t lo = (uint32_t)n, hi = (n >> 32);`, where `n` is a `unit64_t` variable. – jfs Aug 30 '12 at 14:16
39
long long val = (long long) mostSignificantWord << 32 | leastSignificantWord;
printf( "%lli", val );
twk
  • 16,760
  • 23
  • 73
  • 97
  • 4
    `<< 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. – Alex Korban May 04 '10 at 21:25
  • @Alex: in g++ on x64, `long` is 64 bits. On x86 it is 32 bits. – Steve Jessop May 04 '10 at 22:27
  • 33
    Please use C99 types `uint32_t` and `uint64_t`. This is why they are there. – Norman Ramsey May 05 '10 at 00:09
  • 3
    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`. – underscore_d Jan 01 '19 at 22:38
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.

Nils Pipenbrinck
  • 83,631
  • 31
  • 151
  • 221
  • 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. – caf May 04 '10 at 22:00
  • 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. – Adrian McCarthy May 04 '10 at 22:23
  • Use the C99 types with explicit sizes. It is why they are there. – Norman Ramsey May 05 '10 at 00:10
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;
}
caiohamamura
  • 2,260
  • 21
  • 23
  • 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 :( – noname2019 Sep 15 '20 at 05:20
  • 1
    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 Sep 16 '20 at 15:25
  • 1
    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. – caiohamamura Sep 16 '20 at 16:25
  • 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?**](https://stackoverflow.com/questions/46790550/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. – Andrew Henle Jul 05 '21 at 14:26
  • 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. – caiohamamura Jul 05 '21 at 15:54
1

This code works when both upper32 and lower32 is negative:

data64 = ((LONGLONG)upper32<< 32) | ((LONGLONG)lower32& 0xffffffff);
Juan Mellado
  • 14,973
  • 5
  • 47
  • 54
AIMrus
  • 11
  • 1
1

Instead of attempting to print decimal, I often print in hex.

Thus ...

printf ("0x%x%08x\n", upper32, lower32);

Alternatively, depending upon the architecture, platform and compiler, sometimes you can get away with something like ...

printf ("%lld\n", lower32, upper32);

or

printf ("%lld\n", upper32, lower32);

However, this alternative method is very machine dependent (endian-ness, as well as 64 vs 32 bit, ...) and in general is not recommended.

Hope this helps.

Sparky
  • 13,505
  • 4
  • 26
  • 27
-1

You could do it by writing the 32-bit values to the right locations in memory:

unsigned long int data64;
data64=lowerword
*(&((unsigned int)data64)+1)=upperword;

This is machine-dependent however, for example it won't work correctly on big-endian processors.

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
mohit
  • 124
  • 7
  • could you elaborate and explain your answer – dove Oct 19 '12 at 11:04
  • 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. – mohit Oct 20 '12 at 09:30
  • 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` – Peter Cordes Jun 05 '19 at 02:00
-1

Late at the game, but I needed such a thing similar to represent a numerical base10 string of a 64bit integer on a 32bit embedded env..

So, inspired by Link I wrote this code that can do what asked in the question, but not limiting on base10: can convert to any base from 2 to 10, and can be easly extended to base N.

void stringBaseAdd(char *buf, unsigned long add, int base){

    char tmp[65], *p, *q;
    int l=strlen(buf);
    int da1, da2, dar;
    int r;

    tmp[64]=0;
    q=&tmp[64];
    p=&buf[l-1];
    r=0;
    while(add && p>=buf){

        da1=add%base;
        add/=base;
        da2=*p-'0';
        dar=da1+da2+r;

        r=(dar>=base)? dar/base: 0;
        *p='0'+(dar%base);
        --p;
    }

    while(add){

        da1=add%base;
        add/=base;
        dar=da1+r;

        r=(dar>=base)? dar/base: 0;
        --q;
        *q='0'+(dar%base);
    }

    while(p>=buf && r){

        da2=*p-'0';
        dar=da2+r;

        r=(dar>=base)? 1: 0;
        *p='0'+(dar%base);
        --p;
    }

    if(r){

        --q;
        *q='0'+r;
    }

    l=strlen(q);
    if(l){

        memmove(&buf[l], buf, strlen(buf)+1);
        memcpy(buf, q, l);
    }
}
void stringBaseDouble(char *buf, int base){

    char *p;
    int l=strlen(buf);
    int da1, dar;
    int r;

    p=&buf[l-1];
    r=0;
    while(p>=buf){

        da1=*p-'0';
        dar=(da1<<1)+r;

        r=(dar>=base)? 1: 0;
        *p='0'+(dar%base);
        --p;
    }

    if(r){

        memmove(&buf[1], buf, strlen(buf)+1);
        *buf='1';
    }
}
void stringBaseInc(char *buf, int base){

    char *p;
    int l=strlen(buf);
    int da1, dar;
    int r;

    p=&buf[l-1];
    r=1;
    while(p>=buf && r){

        da1=*p-'0';
        dar=da1+r;

        r=(dar>=base)? 1: 0;
        *p='0'+(dar%base);
        --p;
    }

    if(r){

        memmove(&buf[1], buf, strlen(buf)+1);
        *buf='1';
    }
}
void stringLongLongInt(char *buf, unsigned long h, unsigned long l, int base){

    unsigned long init=l;
    int s=0, comb=0;

    if(h){

        comb=1;
        init=h;
        while(!(init&0x80000000L)){

            init<<=1;
            init|=(l&0x80000000L)? 1: 0;
            l<<=1;
            s++;
        }
    }

    buf[0]='0';
    buf[1]=0;
    stringBaseAdd(buf, init, base);

    if(comb){

        l>>=s;
        h=0x80000000L>>s;
        s=sizeof(l)*8-s;
        while(s--){

            stringBaseDouble(buf, base);
            if(l&h)
                stringBaseInc(buf, base);

            h>>=1;
        }
    }
}

If you ask for

char buff[20];
stringLongLongInt(buff, 1, 0, 10);

your buff will contain 4294967296

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
tmx976
  • 57
  • 5