43

I can't seem to find the relevant parts in the C standard fully defining the behavior of the unary minus operator with unsigned operands.

The 2003 C++ standard (yes, C++, bear with me for a few lines) says in 5.3.1c7: 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 1999 C standard, however, doesn't include such an explicit statement and does not clearly define the unary - behavior neither in 6.5.3.3c1,3 nor in 6.5c4. In the latter it says Some operators (the unary operator ~, and the binary operators <<, >>, &, ^, and |, ...) ... return values that depend on the internal representations of integers, and have implementation-defined and undefined aspects for signed types.), which excludes the unary minus and things seem to remain vague.

This earlier question refers to the K&R ANSI C book, section A.7.4.5 that says The negative of an unsigned quantity is computed by subtracting the promoted value from the largest value of the promoted type and adding one.

What would be the 1999 C standard equivalent to the above quote from the book?

6.2.5c9 says: 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.

Is that it? Or is there something else I'm missing?

Community
  • 1
  • 1
Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • 1
    The results of the `<<` and `>>` operators do not depend on the representation of integers. They are defined as multiplication and division by powers of two and are only well-defined only for non-negative operands. `<<` is undefined for negative operands, and `>>` is implementation-defined for negative operands. – R.. GitHub STOP HELPING ICE Nov 06 '11 at 15:02
  • 'The negative of an unsigned quantity is computed by subtracting the promoted value from the largest value of the promoted type and adding one' -- I know my mathemagical kung fu is weak compared to the mighty Ritchie and Stroustrup, but this seems like a poor, and perhaps insane, way to handle a situation that is certainly programmer error 99.9...% of the time. Why not throw a compile error saying 'you have tried to apply a sign to an unsigned type, you dingus'? – CCJ Dec 11 '15 at 16:04
  • @CCJ And then you'd have to workaround this compiler smartness in weird ways like `1 + ~a` to shut the compiler up and someone would not understand what this expression means. :) – Alexey Frunze Dec 12 '15 at 05:27

4 Answers4

22

Yes, 6.2.5c9 is exactly the paragraph that you looked for.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
  • 4
    Alternatively, one could view the negative operator as yielding the additive inverse of its operand. If N is unsigned, -N is equivalent to 1U+~N because that is the value which, when added to N, will yield zero. – supercat Feb 25 '14 at 14:40
  • @supercat So you're guaranteed two's complement behavior even if the underlying hardware uses a different representation of negative numbers? – JAB Jul 19 '17 at 17:02
  • @JAB: Two's-complement representations happen to match unsigned representations, but the definition of the behavior has nothing to do with two's-complement. – supercat Jul 19 '17 at 17:31
16

The behavior of the unary minus operator on unsigned operands has nothing to do with whether a machine uses two's-complement arithmetic with signed numbers. Instead, given unsigned int x,y; the statement y=-x; will cause y to receive whatever value it would have to hold to make x+y equal zero. If x is zero, y will likewise be zero. For any other value of x, it will be UINT_MAX-x+1, in which case the arithmetic value of x+y will be UINT_MAX+1+(y-y) which, when assigned to a unsigned integer, will have UINT_MAX+1 subtracted from it, yielding zero.

supercat
  • 77,689
  • 9
  • 166
  • 211
5

In every implementation I know of, a negative is calculated as two's complement...

int a = 12;
int b = -a;
int c = ~a + 1;
assert(b == c);

...so there is really no physical difference between negative signed and "negative" unsigned integers - the only difference is in how they are interpreted.

So in this example...

unsigned a = 12;
unsigned b = -a;
int c = -a;

...the b and c are going to contain the exact same bits. The only difference is that b is interpreted as 2^32-12 (or 2^64-12), while c is interpreted as "normal" -12.

So, a negative is calculated in the exact same way regardless of "sign-ness", and the casting between unsigned and signed is actually a no-op (and can never cause an overflow in a sense that some bits need to be "cut-off").

Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
3

This is late, but anyway...

C states (in a rather hard way, as mentioned in other answers already) that

  • any unsigned type is a binary representation with a type-specific number of bits

  • all arithmetic operations on unsigned types are done (mod 2^N), 'mod' being the mathematical definition of the modulus, and 'N' being the number of bits used to represent the type.

The unary minus operator applied to an unsigned type behaves as if the value would have been promoted to the next bigger signed type, then negated, and then again converted to unsigned and truncated to the source type. (This is a slight simplification because of integer promotion happens for all types that have fewer bits than 'int', but it comes close enough I think.)

Some compilers do indeed give warnings when applying the unary minus to an unsigned type, but this is merely for the benefit of the programmer. IMHO the construct is well-defined and portable.

But if in doubt, just don't use the unary minus: write '0u - x' instead of '-x', and everything will be fine. Any decent code generator will create just a negate instruction from this, unless optimization is fully disabled.

Pearly
  • 113
  • 1
  • 5
  • If I remember correctly, visual studio didn't like `0u - x` either and so I had to write `1u + ~x` or some such. – Alexey Frunze Nov 05 '20 at 18:44
  • Something's coming back to my memory... right. But then, Microsoft constantly refuses to have a standard compliant C compiler, no matter what they call it. (Well, maybe C89.) They're getting closer, though. They recently even admitted that their runtime checks for assignment to shorter unsigned ints actually violates the C standard and dropped that checks. – Pearly Nov 08 '20 at 20:17