Is (int)(unsigned)-1 == -1 undefined behavior
I am trying to understand the meaning of the statement:
(int)(unsigned)-1 == -1;
To my current understanding the following things happen:
-
-1
is a signedint
and is casted to unsignedint
. The result of this is that due to wrap-around behavior we get the maximum value that can be represented by theunsigned
type. -
Next, this
unsigned
type maximum value that we got in step 1, is now casted tosigned int
. But note that this maximum value is anunsigned type
. So this is out of range of thesigned type
. And since signed integer overflow is undefined behavior, the program will result in undefined behavior.
My questions are:
- Is my above explanation correct? If not, then what is actually happening.
- Is this undefined behavior as i suspected or implementation defined behavior.
PS: I know that if it is undefined behavior(as opposed to implementation defined) then we cannot rely on the output of the program. So we cannot say whether we will always get true
or false
.
Solution 1:
Cast to unsigned int
wraps around, this part is legal.
Out-of-range cast to int
is legal starting from C++20, and was implementation-defined before (but worked correctly in practice anyway). There's no UB here.
The two casts cancel each other out (again, guaranteed in C++20, implementation-defined before, but worked in practice anyway).
Signed overflow is normally UB, yes, but that only applies to overflow caused by a computation. Overflow caused by a conversion is different.
cppreference
If the destination type is signed, the value does not change if the source integer can be represented in the destination type. Otherwise the result is:
(until C++20) implementation-defined
(since C++20) the unique value of the destination type equal to the source value modulo
2n
wheren
is the number of bits used to represent the destination type.(Note that this is different from signed integer arithmetic overflow, which is undefined).
More specifics on how the conversions work.
Lets's say int
and unsigned int
occupy N bits.
The values that are representable by both int
and unsigned int
are unchanged by the conversion. All other values are increased or decreased by 2N to fit into the range.
This conveniently doesn't change the binary representation of the values.
E.g. int
-1
corresponds to unsigned int
2N-1
(largest unsigned int
value), and both are represented as 11...11
in binary. Similarly, int
-2N-1
(smallest int
value) corresponds to unsigned int
2N-1
(largest int
value + 1).
int: [-2^(n-1)] ... [-1] [0] [1] ... [2^(n-1)-1]
| | | | |
| '---|---|-----------|-----------------------.
| | | | |
'---------------|---|-----------|----------. |
| | | | |
V V V V V
unsigned int: [0] [1] ... [2^(n-1)-1] [2^(n-1)] ... [2^n-1]