Applying unary minus to unsigned values ​​in C++

Question:

The language standard repeatedly mentions that operations on unsigned integers are performed modulo 2^n , where n is the number of bits involved in representing the value of the unsigned integer.

Let's have this code:

    unsigned char a = 1;
    a = -a;

And, let unsigned char be eight bits, and int sixteen bits.

I already thought that the expression -a either immediately converts to an unsigned char value equal to 255 ; or a is expanded to an int , then that int becomes -1 , and finally -1 is converted to an unsigned char value of 255 . However, as it turned out, the standard specifies a special, rather strange way to calculate the unary minus for unsigned values.

Here is a quote from paragraph 8.3.1/8 of that document:

The operand of the unary – operator shall have arithmetic or unscoped enumeration type and the result is the negation of its operand. Integral promotion is performed on integral or enumeration operands. The negative of an unsigned quantity is computed by subtracting its value from 2^n , where n is the number of bits in the promoted operand. The type of the result is the type of the promoted operand.

If I understood correctly what was written, then the value of the expression -a in the above example is equal to the value 2^n - 1 , where n is equal to the number of bits in the extended operand. Those. 2^n - 1 == 2^16 - 1 == 65536 - 1 == 65535 . But the given value cannot be represented in a sixteen-bit int , which means that attempting to evaluate -a results in undefined behavior.

The question is: can a unary minus "work" as written above? And if so, why does the standard define such strange behavior of unary minus for unsigned values?


It is also strange that if you write like this:

    unsigned char a = 1;
    a = 0 - a;

then it doesn't seem to lead to undefined behavior. Since 0 is of type int , a will be expanded to int , the difference 0-1 will be computed, and the resulting int value will be safely truncated modulo 256 to 255 .

Answer:

The words about 2 n apply only to unsigned types (arithmetic modulo max() + 1 – the result is always defined). These words do not refer to the int type.

If all unsigned char values ​​of type fit into int (often happens), then (C++ n4659 §7.6.1):

-(unsigned char)1 = (integer promotion, likely) = -(int)1 = (int)-1

If they do not fit, then:

-(unsigned char)1 = (integer promotion, rare) = -(unsigned int)1 
    = (unsigned int)2**n-1 = (by definition) = (unsigned int)UINT_MAX

where n is the number of bits for the values ​​(value bits – may be different from sizeof * CHAR_BIT if there are padding bits – C n1570 §6.2.6.2.1).

That is, the type -a of the expression is int or unsigned int. To assign back to a requires (possibly a narrowing conversion) to unsigned char:

(int)-1 = (UCHAR_MAX + 1) - 1 = (unsigned char)UCHAR_MAX

or:

(unsigned int)UINT_MAX = UINT_MAX % (UCHAR_MAX + 1) = (unsigned char)UCHAR_MAX

i.e. the value is always obtained in this case 2 CHAR_BIT -1 :

a == std::numeric_limits<unsigned char>::max()
Scroll to Top