25

The following results make me really confused:

int i1 = 20-80u;    // -60
int i2 = 20-80;     // -60
int i3 =(20-80u)/2; // 2147483618
int i4 =(20-80)/2;  // -30
int i5 =i1/2;       // -30
  1. i3 seems to be computed as (20u-80u)/2, instead of (20-80u)/2
  2. supposedly i3 is the same as i5.
autistic
  • 1
  • 3
  • 35
  • 80
Leo Lai
  • 863
  • 8
  • 17

3 Answers3

13

IIRC, an arithmetic operation between signed and unsigned int will produce an unsigned result.

Thus, 20 - 80u produces the unsigned result equivalent to -60: if unsigned int is a 32-bit type, that result is 4294967236.

Incidentally, assigning that to i1 produces an implementation-defined result because the number is too large to fit. Getting -60 is typical, but not guaranteed.

  • 5
    _Incidentally, assigning that value to i1 is undefined behavior_ Are you sure about that? I taught that conversion from unsigned int to signed int is well defined for all values of unsigned int. – rozina Sep 09 '15 at 10:46
  • 4
    There is no signed integer overflow here. There are conversions. See [conv.integral](http://eel.is/c++draft/conv.integral). – autistic Sep 09 '15 at 10:56
  • @rozina: Huh, I had never before seen that conversion works differently in this respect. Fixed –  Sep 09 '15 at 11:11
10
int i1 = 20-80u;    // -60

This has subtle demons! The operands are different, so a conversion is necessary. Both operands are converted to a common type (an unsigned int, in this case). The result, which will be a large unsigned int value (60 less than UINT_MAX + 1 if my calculations are correct) will be converted to an int before it's stored in i1. Since that value is out of range of int, the result will be implementation defined, might be a trap representation and thus might cause undefined behaviour when you attempt to use it. However, in your case it coincidentally converts to -60.


int i3 =(20-80u)/2; // 2147483618

Continuing on from the first example, my guess was that the result of 20-80u would be 60 less than UINT_MAX + 1. If UINT_MAX is 4294967295 (a common value for UINT_MAX), that would mean 20-80u is 4294967236... and 4294967236 / 2 is 2147483618.


As for i2 and the others, there should be no surprises. They follow conventional mathematical calculations with no conversions, truncations, overflows or other implementation-defined behaviour what-so-ever.

autistic
  • 1
  • 3
  • 35
  • 80
  • So if I understand this correctly, converting -1 to unsigned is well defined and it is UINT_MAX. But if you then convert UINT_MAX back to int it is suddenly implementation defined? And could not be -1? – rozina Sep 09 '15 at 11:12
  • Nice answer day :) – LPs Feb 12 '17 at 09:16
3

The binary arithmetic operators will perform the usual arithmetic conversions on their operands to bring them to a common type.

In the case of i1, i3 and i5 the common type will be unsigned int and so the result will also be unsigned int. Unsigned numbers will wrap via modulo arithmetic and so subtracting a slightly larger unsigned value will result in a number close to unsigned int max which can not be represented by an int.

So in the case of i1 we end up with an implementation defined conversion since the value can not be represented. In the case of i3 dividing by 2 brings the unsigned value back into the range of int and so we end up with a large signed int value after conversion.

The relevant sections form the C++ draft standard are as follows. Section 5.7 [expr.add]:

The additive operators + and - group left-to-right. The usual arithmetic conversions are performed for operands of arithmetic or enumeration type.

The usual arithmetic conversions are covered in section 5 and it says:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

[...]

  • Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

and for the conversion from a value that can not be represented for a signed type, section 4.7 [conv.integral]:

If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

and for unsigned integers obeys modulo arithmetic section 3.9.1 [basic.fundamental]:

Unsigned integers shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.48

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • @Hurkyl: Damned, I am sleeping standing today, I mangled the unsigned overflow and conversion from unsigned to signed (the latter being implementation defined). I'll self-destruct my comment... – Matthieu M. Sep 09 '15 at 12:30