tags:

views:

465

answers:

4

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?

+9  A: 
long long val = (long long) mostSignificantWord << 32 | leastSignificantWord;
printf( "%lli", val );
twk
`<< 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 - Aotea Studios
Good catch. I fixed it.
twk
@Alex: in g++ on x64, `long` is 64 bits. On x86 it is 32 bits.
Steve Jessop
@Steve: Ah, thanks for the clarification.
Alex - Aotea Studios
Please use C99 types `uint32_t` and `uint64_t`. This is why they are there.
Norman Ramsey
+1  A: 

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
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
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
Use the C99 types with explicit sizes. It is why they are there.
Norman Ramsey
A: 

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
+8  A: 

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

J.F. Sebastian
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
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.
Bei337
@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/
J.F. Sebastian
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.
Bei337
@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.
J.F. Sebastian