7

For example:

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 the two's complement (in conjunction with a +1).

The reason I ask is because I stumbled upon this in some code for a chess engine. Chess engines do a lot of 'hacky' things to get absolute speed, especially in the move generation functions that are called millions of times per second. (It doesn't help that it was an example of magic bitboard move generation - the most optimized of them all). This chess engine code in particular only works correctly under gcc compilation (I suspect).

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

Thanks.

Eric Thoma
  • 5,905
  • 6
  • 30
  • 40

4 Answers4

13

The relevant quote from the Standard is actually this:

(§5.3.1/8) 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 2n, where n is the number of bits in the promoted operand. The type of the result is the type of the promoted operand.

(This is from C++11; it used to be 5.3.1/7 in older versions.)

So -num will be evaluated as 2CHAR_BIT*sizeof(num) - num (‡). The result will be of the same type as the operand (after integer promotion), i.e. it will also be unsigned.

I just tested with GCC and it seems to perform the operation in precisely the way described by the Standard. I'll assume this is the case for Visual C++ as well; otherwise it's a bug.


(‡) This formula assumes that the relevant number of bits corresponds to the size (in bits) of the variable in memory. As Keith Thompson points out in the comment, this can't be true if there are padding bits (i.e. when not all bits participate in the representation of the numerical value, which is possible according to §3.9.1/1). On a system that uses more bits to store the value than are used to represent the numerical value, the formula will not be accurate. (I, personally, am not actually aware of any such system, though.)

jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • 1
    The "number of bits" doesn't include any padding bits (of which there are none in most implementations), so the expression involving `CHAR_BIT` (which I can't reproduce in a comment) isn't *always* accurate. – Keith Thompson Dec 30 '12 at 05:17
  • @KeithThompson Yes, I actually wondered about this. The Standard explicitly says in 3.9.1/1 that in integer types other than `char`, not all bits necessarily participate in the value representation. But in the section quoted above, it says `n` is the number of bits (not the number of bits that participate in the value representation). Clearly, padding bits can't possibly count (otherwise it doesn't make sense), but the wording in the Standard isn't quite precise here. – jogojapan Dec 30 '12 at 05:24
3

Here's what C++ standard says under section 4.7.2 (Integral conversions):

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 ]

Hope this answers your question.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
UnknownGosu
  • 854
  • 6
  • 9
  • That doesn't directly answer the question. There is no conversion in the OP's sample code (other the conversion of `66` from `int` to `unsigned int`). The unary "-" is being applied to an `unsigned` value, and yields an `unsigned` result. (It does explain the meaning of `unsigned int max = -1;`, but that wasn't the question.) – Keith Thompson Dec 30 '12 at 05:21
3

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

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

A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

The description of the unary "-" operator merely says that the result is "the negative of its (promoted) operand"; it assumes that the reader has read 6.2.5 to figure out what the "negative" of an unsigned integer is.

In either language, the result of:

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

is to store UINT_MAX - 66U + 1U in numB. (The U suffixes aren't really necessary, but I include them to emphasize that this is all defined in terms of unsigned values.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
0

I was bitten by the typeof(-Unsigned) is Unsigned. The VS2012 compiler takes this to interesting levels of inscrutable behaviour:

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

What is "y"?

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

I guess that for boundary values, it's all Deathstar 9000. Sigh.

Mischa
  • 2,240
  • 20
  • 18
  • Check precedence table, unary `-` has higher precedence than `/` – M.M Sep 28 '14 at 21:49
  • Thanks. And only too late, I did. The real surprise, of course, was that "-(unsigned)" is of type unsigned. – Mischa Sep 29 '14 at 22:38