Why are these two numbers comparing equal?

Viewed 268

I'm wondering why it's the case that these two numbers are comparing equal. I had a (maybe false) realisation that I messed up my enums, because in my enums I often do:

enum class SomeFlags : unsigned long long { ALL = ~0, FLAG1 = 1, FLAG2 };

And I thought that 0 being an int and being assigned to an unsigned long long, that there was a mistake.

unsigned long long number1 = ~0;
/* I expect the 0 ( which is an int, to be flipped, which would make it
the max value of an unsigned int. It would then get promoted and be 
assigned that value to an unsigned long long. So number1 should
have the value of max value of unsigned int*/



int main()
{
    unsigned long long number2 = unsigned long long(0);
    number2 = number2 - 1;
    /* Here I expect number2 to have the max value of unsigned long long*/

    if (number1 == number2)
    {
        std::cout << "Is same\n";
        // They are the same, but how???
    }
}

This is really confusing. To emphasise the point:

unsigned long long number1 = int(~0);
/* I feel like I'm saying:
- initialize an int with the value of zero with its bits flipped
- assign it to the unsigned long long

And this somehow ends up as the max value of unsigned long long???*/
5 Answers

cppreference: "The result of operator~ is the bitwise NOT (one's complement) value of the argument (after promotion."

No promotion (to int or unsigned int) is however needed here - but flipping all bits in a signed type with the value 0 will make it -1 (all bits set) which when converted to an unsigned type will be the largest value that type can hold.

To avoid this, make 0 unsigned. The result of ~0U will be an unsigned int with all bits set, which can be converted to any larger unsigned type perfectly.

unsigned long long number1 = ~0U;

You seems to be thinking that the conversion of int to long long is done just by prepending 0 in front of the int. While this is true for the conversion of positive integer, it's not true for negative integers.

unsigned long long number1 = ~0

is identical as

unsigned long long number1 = -1

So when the conversion takes place, it is not simply copying the bits into number1

Since C++11 unsigned arithmetic must use modulo wrapping.

Which means that if you have N bits then any operation OP works like this:

result = (a OP b) % (2^N)

Say you have 8 bits and substract as operation.

unsigned char result1 = (0 - 1) % 256 = -1 % 256 = 255 = 0b11111111
unsigned char result2 = ~(0b00000000) = 0b11111111 = 255

The same holds for larger N and thus your result

In C/C++ integer promotion is governed by the type of the SOURCE argument, not the DESTINATION.

So - the promotion from signed integer types will be sign extended - regardless the type of the destination, be it signed or unsigned.

The promotion from unsigned integer types will be zero extended - again, regardless the type of destination.

In your case 0 is signed integer and ~0 is also signed integer. This will be sign extended to unsigned long long.

The conversion from int to unsigned long long is specified based on the value of the int (aka "the source integer") not the bit pattern. The value is -1 in this case:

4.7 Integral conversions [conv.integral]

A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type.

If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2^n where n is the number of bits used to represent the unsigned type). [ Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). — end note ]

For an integer with a value of -1 the "least unsigned integer congruent to the source integer" modulo 2^64 is (-1 + 2^64).

The simple-to-understand effect of this rule is that int is sign-extended when being converted to an unsigned type.

Related