What would you expect this C++ code to do?

uint8_t val = ~0;
If (val == ~0) {
  printf("YES!");
} else {
  printf("NO!");
}

The result: NO!

How about this?

#define DO_TEST( TEST_TYPE ) \
{ \
  TEST_TYPE val = ~0; \
  if (val == ~0 ) { \
    printf(#TEST_TYPE ": equal to ~0\n"); \
  } else { \
    printf(#TEST_TYPE ": NOT equal to ~0\n"); \
  } \
} \

DO_TEST(uint8_t);
DO_TEST(uint16_t);
DO_TEST(uint32_t);
DO_TEST(uint64_t);

DO_TEST(int8_t);
DO_TEST(int16_t);
DO_TEST(int32_t);
DO_TEST(int64_t);

The result:

uint8_t: NOT equal to ~0
uint16_t: NOT equal to ~0
uint32_t: equal to ~0
uint64_t: equal to ~0
int8_t: equal to ~0
int16_t: equal to ~0
int32_t: equal to ~0
int64_t: equal to ~0

What is so different about uint8_t and uint16_t? … And why do all the signed types seem unaffected?

Let’s update the test code to use a literal size-suffix:

  if (val == ~0ll ) {  

This will ensure that the literal is a 64-bit value. The new result:

uint8_t: NOT equal to ~0
uint16_t: NOT equal to ~0
uint32_t: NOT equal to ~0
uint64_t: equal to ~0
int8_t: equal to ~0
int16_t: equal to ~0
int32_t: equal to ~0
int64_t: equal to ~0

The uint32_t test now fails also.

What is going on here?

The bitwise negation operator, ~, applied to 0results in a number with all bits set, regardless of the signed-ness.

Let’s write the uint8_t expression another way by making Val a literal as well:

0xff == ~0

This evaluates to false also.

Let’s also expand out the ~0 to the 32bit default int size.

0xff == 0xffffffff

We can see more clearly now that the values are indeed not equal.

But why does it succeed when val has more bits than the 32 of default int? … Automatic Integral Promotion!

Integral promotion is effective in ensuring that intermediate calculations involving smaller types do not overflow. In the case of the equality operator, both operands are expanded to the size of the larger of the two.

Let’s expand the expression for the uint_64 case:

0xffffffffffffffff == ~0x0000000000000000

After applying the binary negation, it is clearly true:

0xffffffffffffffff == 0xffffffffffffffff

But what about the signed cases, which always work regardless of integral promotion?

Let’s look at the binary representation of -1 with varying type sizes:

8-bits: 11111111
16-bits: 1111111111111111
32-bits: 11111111111111111111111111111111

The first bit indicates that this is a negative number. These negative numbers simply are interpretations of unsigned numbers that wrap around when counting below zero. This representation is known as Two’s Complement.

When integral promotion is applied, the numerical value of the negative number is maintained. On a binary level, this is effectively done by repeating the first, “sign bit”, to fill in the newly added bytes.

Although ~0 stored in a uint8_t and a int8_t both have the same binary representation, the method of integral promotion applied by the compiler differs.

Conclusion

Integral promotion in the expression comparing the two values occurs before the ~ operator. Both the ~0 and val are promoted to the largest type between the two. If val is the larger type, the condition can succeed; otherwise, it will always fail.

In the case of negative numbers, integral promotion extends the sign bit to maintain the same numerical value in two’s complement encoding. This results in the values continuing to be equal after promotion.