For the official answer - Section 4.7 conv.integral for conversion from signed integral types.
"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). [ 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 ]
This essentially means that if the underlying architecture stores in a method that is not Two's Complement (like Signed Magnitude, or One's Complement), that the conversion to unsigned must behave as if it was Two's Complement.
(C++20 and later require that signed integers use 2's complement, but earlier versions of the standard allowed those other representations.)
The "... congruent ..." part means that you add or subtract 2n until the value is in the value-range of the unsigned type. For 2's complement, this means doing 2's complement sign-extension or truncation. For the same width, the bit-pattern is unchanged because adding 2n is a no-op: the low n
bits of 2n are all zero. 2's complement addition/subtraction are the same bitwise operation as unsigned, which is what makes it special.
PS. conversion from floating point to unsigned works differently: it's undefined behavior if the value is out of range for the unsigned type (if it's negative after truncation to integer or too large). Modulo reduction only happens for signed integer to unsigned integer.
nVal = (unsigned int) -5;
. The cast of-5
tounsigned int
is defined in 6.3.1.3. The representation in 2s complement is not mandated by the standard but the algorithm to convert to unsigned is: "the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the newtype until the value is in the range of the newtype." – Fennell