Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring 64-bit variables in C

Tags:

c

bit-shift

I have a question.

uint64_t var = 1; // this is 000000...00001 right? 

And in my code this works:

var ^ (1 << 43) 

But how does it know 1 should be in 64 bits? Shouldn’t I write this instead?

var ^ ( (uint64_t) 1 << 43 ) 
like image 846
David 天宇 Wong Avatar asked Oct 18 '13 13:10

David 天宇 Wong


People also ask

Does C support 64-bit?

Microsoft C/C++ features support for sized integer types. You can declare 8-, 16-, 32-, or 64-bit integer variables by using the __intN type specifier, where N is 8, 16, 32, or 64.

What is 64-bit integer in C?

The long long data type makes handling 64 bit integers easy. In C language, an unsigned number over 32 bits cannot exceed the value of 4,294,967,295. You may find you are required to handle larger numbers and for this you need these numbers to be coded in 64-bit.

How do you write a 64-bit number?

You can use uint64_t type and "%" SCNu64 format specifier for unsigned 64-bit integer.

Is long 64-bit in C?

long , ptr , and off_t are all 64 bits (8 bytes) in size. The 32-bit data model for z/OS® XL C/C++ compilers is ILP32 plus long long.


2 Answers

As you supposed, 1 is a plain signed int (which probably on your platform is 32 bit wide in 2's complement arithmetic), and so is 43, so by any chance 1<<43 results in an overflow: in facts, if both arguments are of type int operator rules dictate that the result will be an int as well.

Still, in C signed integer overflow is undefined behavior, so in line of principle anything could happen. In your case, probably the compiler emitted code to perform that shift in a 64 bit register, so by luck it appears to work; to get a guaranteed-correct result you should use the second form you wrote, or, in alternative, specify 1 as an unsigned long long literal using the ull suffix (unsigned long long is guaranteed to be at least 64 bit).

var ^ ( 1ULL << 43 ) 
like image 171
Matteo Italia Avatar answered Oct 07 '22 19:10

Matteo Italia


I recommend OP's approach, cast the constant ( (uint64_t) 1 << 43 )

For OP's small example, the 2 below will likely perform the same.

uint64_t var = 1;  // OP solution) var ^ ( (uint64_t) 1 << 43 ) // Others suggested answer var ^ ( 1ULL << 43 )         

The above results have the same value, but different types. The potential difference lies in how 2 types exist in C: uint64_t and unsigned long long and what may follow.

uint64_t has an exact range 0 to 264-1.
unsigned long long has a range 0 to at least 264-1.

If unsigned long long will always be 64-bits, as it seems to be on many a machine there days, there is no issue, but let's look to the future and say this code was run on a machine where unsigned long long was 16 bytes (0 to at least 2128-1).

A contrived example below: The first result of the ^ is a uint64_t, when multiplied by 3, the product will still be uint64_t, performing a modulo 264, should overflow occur, then the result is assigned to d1. In the next case, the result of ^ is an unsigned long long and when multiplied by 3, the product may be bigger than 264 which is then assigned to d2. So d1 and d2 have a different answer.

double d1, d2; d1 = 3*(var ^ ( (uint64_t) 1 << 43 )); d2 = 3*(var ^ ( 1ULL << 43 )); 

If one wants to work with unit64_t, be consistent. Do not assume unit64_t and unsigned long long are the same. If it is OK for your answer to be a unsigned long long, fine. But in my experience, if one starts using fixed sized types like uint64_t, one does not want variant size types messing up the computations.

like image 40
chux - Reinstate Monica Avatar answered Oct 07 '22 19:10

chux - Reinstate Monica