'Casting both bitwidth and signed/unsigned, which conversion is executed first?

Consider the following code:

int32_t x = -2;
cout << uint64_t(x) << endl;

The cast in the second line contains basically two atomic steps. The increase in bitwidth from 32 bits to 64 bits and the change of interpretation from signed to unsigned. If one compiles this with g++ and executes, one gets 18446744073709551614. This suggests that the increase in bitwidth is processed first (as a signed extension) and the change in signed/unsigned interpretation thereafter, i.e. that the code above is equivalent to writing:

int32_t x = -2;
cout << uint64_t(int64_t(x)) << endl;

What confuses me that one could also first interpret x as an unsigned 32-bit bitvector first and then zero-extend it to 64-bit, i.e.

int32_t x = -2;
cout << uint64_t(uint32_t(x)) << endl;

This would yield 4294967294. Would someone please confirm that the behavior of g++ is required by the standard and is not implementation defined? I would be most excited if you could refer me to the norm in the standard that actually concerns the issue at hand. I tried to do so but failed bitterly.

Thanks in advance!



Solution 1:[1]

You are looking for Standard section 4.7. In particular, paragraph 2 says:

If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type).

In the given example, we have that 18446744073709551614 = -2 mod 264.

Solution 2:[2]

As said by @aschepler, standard 4.7 §2 (Integral conversions) ensures that the result will be least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type)

So in your case, it will be 0xFFFFFFFFFFFFFFFE == 18446744073709551614

But this is a one step conversion as specified by the standard (what compiler actually does is out of scope)

If you want first unsigned conversion to uint32_t and then conversion to uint64_t, you have to specify 2 conversions : static_cast<uint64_t>(static_cast<uint32_t>(-2))

Per 4.7 §2, first will give 0xFFFFFFFE = 4294967294 but as this number is already a valid uint64_t it is unchanged by the second conversion.

What you observed is required by the standard and will be observable on any conformant compiler (provided uint32_t and uint64_t are defined, because this part is not required ...)

Solution 3:[3]

This is an old question but I recently came into this problem. I was using char, which happened to be signed in my computer. I wanted to multiply two values by

char a, b;
uint16 ans = uint16(a) * uint16(b);

However, because of the conversion, when a < 0, the answer is wrong.

Since the signedness of char is implementation-dependent, maybe we should use uint8 instead of char whenever possible.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Holger Watt
Solution 2 Serge Ballesta
Solution 3 youkaichao