What does the unary "-" operator do on unsigned data types in C / C ++ (and on different compilers)?

For instance:

unsigned int numA = 66; // or anything really unsigned int numB = -numA; unsigned int numC = numA & numB 

I understand that the bitwise complement operator can be used to get two additions (in combination with +1).

I ask because I came across this in some code for a chess engine. Chess engines do a lot of “hacked” things to get absolute speed, especially in motion generation functions that are called millions of times per second. (It doesn’t help that it was an example of generating bit magic movements - the most optimized of them). This code in chess code, in particular, only works correctly when compiling gcc (I suspect).

How do different compilers do this? In particular, how gcc handles this compared to the C ++ compiler in VS Studio 2012 Express.

Thanks.

+6
source share
4 answers

The relevant quote from the Standard is actually this:

(§5.3.1 / 8) The operand of a unary operator must have an arithmetic or non-enumerated type of enumeration, and the result is the negation of its operand. Integral promotion is performed on integral or enumerating operands. The negation of the unsigned value is calculated by subtracting its value from 2 n where n is the number of bits in the advanced operand. The result type is the type of the advanced operand.

(This is from C ++ 11, in older versions it was 5.3.1 / 7.)

So, -num will be evaluated as 2 CHAR_BIT * sizeof (num) - num (& ddagger;) . The result will be of the same type as the operand (after whole promotion), i.e. It will also be unsigned.

I just checked with GCC and it seems to have performed the operation exactly as described in the standard. I assume this is true for Visual C ++; otherwise it is a mistake.


(& ddagger;) This formula assumes that the corresponding number of bits corresponds to the size (in bits) of the variable in memory. As Keith Thompson points out in a comment, this cannot be true if there are padding bits (i.e. when not all bits are involved in representing a numerical value, which is possible in accordance with clause 9.1.1 / 1). In a system that uses more bits to store a value than is used to represent a numerical value, the formula will be inaccurate. (I personally do not know about any such system.)

+12
source

Here is what the C ++ standard says in section 4.7.2 (Integral Transformations):

If no destination type is specified, the resulting value is the smallest unsigned integer that matches the source integer (modulo 2 n where n is the number of bits used to represent the unsigned type). [Note: in the representation of a double complement, this conversion is conceptual and there are no changes in the bit scheme (if there is no truncation). -end note]

Hope this answers your question.

+3
source

You asked about C and C ++. Remember that these are two different languages. In this particular case, they have the same rules for operations with unsigned types, but they are expressed differently.

Quote the latest draft of the current (2011) ISO C standard), section 6.2.5p9:

Computing using unsigned operands can never overflow, because a result that cannot be represented by an unsigned integer type decreases modulo a number that is greater than one largest value that can be represented by the resulting type.

The description of the unary operator "-" simply says that the result is "denial of its (advanced) operand"; he suggests that the reader read 6.2.5 to find out what a “negative” unsigned integer is.

In any language, the result:

 unsigned int numA = 66; unsigned int numB = -numA; 

- store UINT_MAX - 66U + 1U in numB . ( U suffixes are not really needed, but I include them to emphasize that all of this is defined in terms of unsigned values.)

+2
source

I was bitten by a type (-Unsigned) that was not signed. The VS2012 compiler takes this to interesting levels of incomprehensible behavior:

unsigned x = 0xFFFFFFFE; int y = -x / 2;

What is the "y"?

I would expect x / 2 = 0x7FFFFFFF, then - (x / 2) = 0x80000001, i.e. -2 ** 31-1. Instead, the compiler generates (-x) = 0x00000002, (-x) / 2 = 0x00000001.

I assume that for boundary values ​​it's all Deathstar 9000. Sigh.

0
source

All Articles