1

So I was playing around with types and I came out with this weird result below. Debugging it made no sense, and then the only result was to check out the c++ spects, which didn't helped much. I was wondering if you might know what is happening here exactly, and if it is 32Bit and/or 64Bit specific issue.

#include <iostream>
using namespace std;

int main() {
    unsigned int u = 1;
    signed int i = 1;

    long long lu = -1 * u;
    long long li = -1 * i;

    std::cout<<"this is a weird " << lu << " " << li << std::endl;

    return 0;
}

Where the output is

this is a weird 4294967295 -1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62

4 Answers4

5

The key observation is that the expression -1 * u is of type unsigned int. That is because the rules for arithmetic conversions* say that if one operand is unsigned int and the other is signed int, then the latter operand is converted to unsigned int. The arithmetic expressions are ultimately only defined for homogeneous operands, so the conversions happen before the operation proper.

The result of the conversion of -1 to unsigned int is a large, positive number, which is representable as a long long int, and which is the number you see in the output.

Currently, that's [expr]/(11.5.3).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
1

The evaluation of

-1 * i

is trivial multiplication of two int types: nothing strange there. And a long long must be capable of holding any int.


First note is there is no such thing as a negative literal in C++, so

-1 * u

is evaluated as (-1) * u due to operator precedence. The type of (-1) must be int. But this will be converted to unsigned int due to C++'s rule of argument conversion as the other argument is an unsigned int In doing that it is converted modulo UINT_MAX + 1, so you end up with UINT_MAX multiplied by 1, which is the number you observe, albeit converted to a long long type.

As a final note, the behaviour of this conversion is subject to the rules of conversion from an unsigned to a signed type: if unsigned int and long long were both 64 bits on your platform then the behaviour is implementation-defined.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

The type of -1 is signed int. When you perform an arithmetic operation between objects of different fundamental type, one or both of the arguments will be converted so that both have the same type. (For non-fundamental types, there may be operator overloads for mixed operands). In this case, the signed value is converted to unsigned, following the conversion rules .

So, -1 was converted to unsigned. But negative numbers cannot be represented by unsigned types. What happens, is that the resulting value will be the smallest positive value that can be represented by the unsigned type, that is congruent with the original signed value modulo the maximum value representable by unsigned type. Which on your platform happens to be 4294967295.


The rules ([expr], standard draft):

... rules that apply to non-integers ...

Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following rules shall be applied to the promoted operands:

— If both operands have the same type, no further conversion is needed.

— Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank.

— 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. (this applies to your case)

— Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.

— Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • It's not true in general that "one of the arguments will be converted to the other type". It is true in general that "both arguments may be converted to some type". See [expr]/(11.5.5). – Kerrek SB Jan 18 '17 at 13:25
  • @KerrekSB thanks, hopefully the wording is better now. – eerorika Jan 18 '17 at 13:29
  • It's still not correct. The converted type may be signed, too, e.g. see [expr]/(11.5.4). – Kerrek SB Jan 18 '17 at 13:30
  • @KerrekSB I'm still making too generic statements, I see. I've now included the rules to be exact. – eerorika Jan 18 '17 at 13:40
  • Thanks, it's good to see the actual rules. They're somewhat subtle and complex, and best appreciated in their original form rather than in paraphrase. – Kerrek SB Jan 18 '17 at 13:41
  • 1
    On a complete tangent, one consequence of the arithmetic conversions is that it is never safe to use the unsigned, sized aliases (`std::uintN_t`) for modulo-2^N arithmetic: You can never know whether `std::uintN_t` is arithmetic-converted to a signed type, because the aliases do not make any promise about conversion rank. This is true even for homogeneous expressions like `a + b` where both operands are of type `std::uintN_t`. – Kerrek SB Jan 18 '17 at 13:43
0

The bit pattern "0xFFFFFFFF" corresponds with "-1" when interpreted as a 32b signed integer and corresponds with "4294967295" when interpreted as a 32b unsigned integer.

  • If used -2 the result is "4294967294"
  • If used -3 the result is "4294967293"
  • If used -4 the result is "4294967292"
  • ....
Tito
  • 1
  • 1