C++ Implicit Conversion (Signed + Unsigned)
Asked Answered
B

3

18

I understand that, regarding implicit conversions, if we have an unsigned type operand and a signed type operand, and the type of the unsigned operand is the same as (or larger) than the type of the signed operand, the signed operand will be converted to unsigned.

So:

unsigned int u = 10;  
signed int s = -8;

std::cout << s + u << std::endl;

//prints 2 because it will convert `s` to `unsigned int`, now `s` has the value
//4294967288, then it will add `u` to it, which is an out-of-range value, so,
//in my machine, `4294967298 % 4294967296 = 2`

What I don't understand - I read that if the signed operand has a larger type than the unsigned operand:

  • if all values in the unsigned type fit in the larger type then the unsigned operand is converted to the signed type

  • if the values in the unsigned type don't fit in the larger type, then the signed operand will be converted to the unsigned type

so in the following code:

signed long long s = -8;
unsigned int u = 10;
std::cout << s + u << std::endl;

u will be converted to signed long long because int values can fit in signed long long??

If that's the case, in what scenario the smaller type values won't fit in the larger one?

Benildis answered 24/7, 2013 at 11:33 Comment(1)
Your first example isn't very good, since the result should be 2 either way. If you want to see if the value is signed or not after the calculation, use a value where the result is negative.Rhoades
E
29

Relevant quote from the Standard:

5 Expressions [expr]

10 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:

[2 clauses about equal types or types of equal sign omitted]

— 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.

— 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.

Let's consider the following 3 example cases for each of the 3 above clauses on a system where sizeof(int) < sizeof(long) == sizeof(long long) (easily adaptable to other cases)

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;

signed long int s2 = -4;
unsigned int u2 = 2;

signed long long int s3 = -4;
unsigned long int u3 = 2;

int main()
{
    std::cout << (s1 + u1) << "\n"; // 4294967294
    std::cout << (s2 + u2) << "\n"; // -2 
    std::cout << (s3 + u3) << "\n"; // 18446744073709551614  
}

Live example with output.

First clause: types of equal rank, so the signed int operand is converted to unsigned int. This entails a value-transformation which (using two's complement) gives te printed value.

Second clause: signed type has higher rank, and (on this platform!) can represent all values of the unsigned type, so unsigned operand is converted to signed type, and you get -2

Third clause: signed type again has higher rank, but (on this platform!) cannot represent all values of the unsigned type, so both operands are converted to unsigned long long, and after the value-transformation on the signed operand, you get the printed value.

Note that when the unsigned operand would be large enough (e.g. 6 in these examples), then the end result would give 2 for all 3 examples because of unsigned integer overflow.

(Added) Note that you get even more unexpected results when you do comparisons on these types. Lets consider the above example 1 with <:

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;
int main()
{
    std::cout << (s1 < u1 ? "s1 < u1" : "s1 !< u1") << "\n";  // "s1 !< u1"
    std::cout << (-4 < 2u ? "-4 < 2u" : "-4 !< 2u") << "\n";  // "-4 !< 2u"
}

Since 2u is made unsigned explicitly by the u suffix the same rules apply. And the result is probably not what you expect when comparing -4 < 2 when writing in C++ -4 < 2u...

Edomite answered 24/7, 2013 at 11:56 Comment(5)
+1 Nice complete answer. BTW: May wish to note this is for 4-byte ints and 8-byte long long. Others may be different.Cowbird
@chux I have reduced my example to the 3 clauses in the Standard, and also indicated where the platform dependencies are.Edomite
@chux the platform I used (Coliru online compiler) is 64-bits where sizeof(long) == sizeof(long long). You probably use 32-bits, where sizeof(long) == sizeof(int), so the 2nd and 3rd cases are interchanged. The Standard is phrased in ranges that can be represented, which is why I used the phrase "on this platform" in my answer.Edomite
Maybe use types with more explicit relative ranks like short, long, long long and avoid size int to reduce ambiguity in your fine answer?Cowbird
@chux apart from the relative ranking in terms of <= the Standard does not provide many absolute size guarantees for integral types. furthermore, for char and short there are also integral promotions to int, prior to the arithmetic conversions. I think it would only complicate the answer. The Standard reasoning + the code example should be clear enough to resolve every practical case.Edomite
M
2

signed int does not fit into unsigned long long. So you will have this conversion: signed int -> unsigned long long.

Motorbus answered 24/7, 2013 at 11:44 Comment(0)
S
1

Note that the C++11 standard doesn't talk about the larger or smaller types here, it talks about types with lower or higher rank.

Consider the case of long int and unsigned int where both are 32-bit. The long int has a larger rank than the unsigned int, but since long int and unsigned int are both 32-bit, long int can't represent all the values of unsigned int.

Therefore we fall into to the last case (C++11: 5.6p9):

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

This means that both the long int and the unsigned int will be converted to unsigned long int.

Sorenson answered 24/7, 2013 at 12:40 Comment(2)
But in the same time unsigned long cannot represent all the values of long int (-1 for example). Why, then, long int converts to unsigned int and not in the opposite way?Motorbus
@sasha.sochka: Actually they are both converted to unsigned long int (I made a mistake initially).Sorenson

© 2022 - 2024 — McMap. All rights reserved.