right shift conversion in C++ [duplicate] - c++

This post is meant to be used as a FAQ regarding implicit integer promotion in C, particularly implicit promotion caused by the usual arithmetic conversions and/or the integer promotions.
Example 1)
Why does this give a strange, large integer number and not 255?
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
Example 2)
Why does this give "-1 is larger than 0"?
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
Example 3)
Why does changing the type in the above example to short fix the problem?
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
puts("-1 is larger than 0"); // will not print
(These examples were intended for a 32 or 64 bit computer with 16 bit short.)

C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.
The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.
Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.
Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.
Integer types and conversion rank
The integer types in C are char, short, int, long, long long and enum.
_Bool/bool is also treated as an integer type when it comes to type promotions.
All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:
Every integer type has an integer conversion rank defined as follows:
— No two signed integer types shall have the same rank, even if they have the same representation.
— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
— The rank of char shall equal the rank of signed char and unsigned char.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.
Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):
The following may be used in an expression wherever an int or unsigned int may
be used:
— An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.
The integer promotions
Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.
Formally, the rule says (C11 6.3.1.1):
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.
This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.
This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!
The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.
This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.
This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.
With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.
The usual arithmetic conversions
Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual artihmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:
(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )
6.3.1.8 Usual arithmetic conversions
Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the usual arithmetic conversions:
First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.
This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.
The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.

According to the previous post, I want to give more information about each example.
Example 1)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since unsigned char is smaller than int, we apply the integer promotion on them, then we have (int)x-(int)y = (int)(-1) and unsigned int (-1) = 4294967295.
The output from the above code:(same as what we expected)
4294967295
-1
How to fix it?
I tried what the previous post recommended, but it doesn't really work.
Here is the code based on the previous post:
change one of them to unsigned int
int main(){
unsigned int x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since x is already an unsigned integer, we only apply the integer promotion to y. Then we get (unsigned int)x-(int)y. Since they still don't have the same type, we apply the usual arithmetic converions, we get (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
Similarly, the following code gets the same result:
int main(){
unsigned char x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
change both of them to unsigned int
int main(){
unsigned int x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since both of them are unsigned int, no integer promotion is needed. By the usual arithmetic converison(have the same type), (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
One of possible ways to fix the code:(add a type cast in the end)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
unsigned char z = x-y;
printf("%u\n", z);
}
The output from the above code:
4294967295
-1
255
Example 2)
int main(){
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
printf("%u\n", a+b);
}
Since both of them are integers, no integer promotion is needed. By the usual arithmetic conversion, we get (unsigned int)a+(unsigned int)b = 1+4294967294 = 4294967295.
The output from the above code:(same as what we expected)
-1 is larger than 0
4294967295
How to fix it?
int main(){
unsigned int a = 1;
signed int b = -2;
signed int c = a+b;
if(c < 0)
puts("-1 is smaller than 0");
printf("%d\n", c);
}
The output from the above code:
-1 is smaller than 0
-1
Example 3)
int main(){
unsigned short a = 1;
signed short b = -2;
if(a + b < 0)
puts("-1 is smaller than 0");
printf("%d\n", a+b);
}
The last example fixed the problem since a and b both converted to int due to the integer promotion.
The output from the above code:
-1 is smaller than 0
-1
If I got some concepts mixed up, please let me know. Thanks~

Integer and floating point rank and promotion rules in C and C++
I'd like to take a stab at this to summarize the rules so I can quickly reference them. I've fully studied the question and both of the other two answers here, including the main one by #Lundin. If you want more examples beyond the ones below, go study that answer in detail as well, while referencing my "rules" and "promotion flow" summaries below.
I've also written my own example and demo code here: integer_promotion_overflow_underflow_undefined_behavior.c.
Despite normally being incredibly verbose myself, I'm going to try to keep this a short summary, since the other two answers plus my test code already have sufficient detail via their necessary verbosity.
Integer and variable promotion quick reference guide and summary
3 simple rules
For any operation where multiple operands (input variables) are involved (ex: mathematical operations, comparisons, or ternary), the variables are promoted as required to the required variable type before the operation is performed.
Therefore, you must manually, explicitly cast the output to any desired type you desire if you do not want it to be implicitly chosen for you. See the example below.
All types smaller than int (int32_t on my 64-bit Linux system) are "small types". They cannot be used in ANY operation. So, if all input variables are "small types", they are ALL first promoted to int (int32_t on my 64-bit Linux system) before performing the operation.
Otherwise, if at least one of the input types is int or larger, the other, smaller input type or types are promoted to this largest-input-type's type.
Example
Example: with this code:
uint8_t x = 0;
uint8_t y = 1;
...if you do x - y, they first get implicitly promoted to int (which is int32_t on my 64-bit
system), and you end up with this: (int)x - (int)y, which results in an int type with value
-1, rather than a uint8_t type of value 255. To get the desired 255 result, manually
cast the result back to uint8_t, by doing this: (uint8_t)(x - y).
Promotion flow
The promotion rules are as follows. Promotion from smallest to largest types is as follows.
Read "-->" as "gets promoted to".
The types in square brackets (ex: [int8_t]) are the typical "fixed-width integer types" for the given standard type on a typical 64-bit Unix (Linux or Mac) architecture. See, for example:
https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
And even better, test it for yourself on your machine by running my code here!: stdint_sizes.c from my eRCaGuy_hello_world repo.
1. For integer types
Note: "small types" = bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t].
SMALL TYPES: bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t]
--> int [int32_t]
--> unsigned int [uint32_t]
--> long int [int64_t]
--> unsigned long int [uint64_t]
--> long long int [int64_t]
--> unsigned long long int [uint64_t]
Pointers (ex: void*) and size_t are both 64-bits, so I imagine they fit into the uint64_t category above.
2. For floating point types
float [32-bits] --> double [64-bits] --> long double [128-bits]

I would like to add two clarifications to #Lundin's otherwise excellent answer, regarding example 1, where there are two operands of identical integer type, but are "small types" that require integer promotion.
I'm using the N1256 draft since I don't have access to a paid copy of the C standard.
First: (normative)
6.3.1.1's definition of integer promotion isn't the triggering clause of actually doing integer promotion. In reality it is 6.3.1.8 Usual arithmetic conversions.
Most of the time, the "usual arithmetic conversions" apply when the operands are of different types, in which case at least one operand must be promoted. But the catch is that for integer types, integer promotion is required in all cases.
[clauses of floating-point types come first]
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Second: (non-normative)
There is an explicit example cited by the standard to demonstrate this:
EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the "integer promotions" require that the abstract machine promote the value of each variable to int size
and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
produce the same result, possibly omitting the promotions.

Related

Does C++ auto cast from ASCII code to relative value when assigning arithmetic operation to char variable? [duplicate]

This post is meant to be used as a FAQ regarding implicit integer promotion in C, particularly implicit promotion caused by the usual arithmetic conversions and/or the integer promotions.
Example 1)
Why does this give a strange, large integer number and not 255?
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
Example 2)
Why does this give "-1 is larger than 0"?
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
Example 3)
Why does changing the type in the above example to short fix the problem?
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
puts("-1 is larger than 0"); // will not print
(These examples were intended for a 32 or 64 bit computer with 16 bit short.)
C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.
The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.
Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.
Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.
Integer types and conversion rank
The integer types in C are char, short, int, long, long long and enum.
_Bool/bool is also treated as an integer type when it comes to type promotions.
All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:
Every integer type has an integer conversion rank defined as follows:
— No two signed integer types shall have the same rank, even if they have the same representation.
— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
— The rank of char shall equal the rank of signed char and unsigned char.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.
Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):
The following may be used in an expression wherever an int or unsigned int may
be used:
— An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.
The integer promotions
Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.
Formally, the rule says (C11 6.3.1.1):
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.
This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.
This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!
The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.
This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.
This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.
With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.
The usual arithmetic conversions
Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual artihmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:
(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )
6.3.1.8 Usual arithmetic conversions
Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the usual arithmetic conversions:
First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.
This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.
The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.
According to the previous post, I want to give more information about each example.
Example 1)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since unsigned char is smaller than int, we apply the integer promotion on them, then we have (int)x-(int)y = (int)(-1) and unsigned int (-1) = 4294967295.
The output from the above code:(same as what we expected)
4294967295
-1
How to fix it?
I tried what the previous post recommended, but it doesn't really work.
Here is the code based on the previous post:
change one of them to unsigned int
int main(){
unsigned int x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since x is already an unsigned integer, we only apply the integer promotion to y. Then we get (unsigned int)x-(int)y. Since they still don't have the same type, we apply the usual arithmetic converions, we get (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
Similarly, the following code gets the same result:
int main(){
unsigned char x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
change both of them to unsigned int
int main(){
unsigned int x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since both of them are unsigned int, no integer promotion is needed. By the usual arithmetic converison(have the same type), (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
One of possible ways to fix the code:(add a type cast in the end)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
unsigned char z = x-y;
printf("%u\n", z);
}
The output from the above code:
4294967295
-1
255
Example 2)
int main(){
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
printf("%u\n", a+b);
}
Since both of them are integers, no integer promotion is needed. By the usual arithmetic conversion, we get (unsigned int)a+(unsigned int)b = 1+4294967294 = 4294967295.
The output from the above code:(same as what we expected)
-1 is larger than 0
4294967295
How to fix it?
int main(){
unsigned int a = 1;
signed int b = -2;
signed int c = a+b;
if(c < 0)
puts("-1 is smaller than 0");
printf("%d\n", c);
}
The output from the above code:
-1 is smaller than 0
-1
Example 3)
int main(){
unsigned short a = 1;
signed short b = -2;
if(a + b < 0)
puts("-1 is smaller than 0");
printf("%d\n", a+b);
}
The last example fixed the problem since a and b both converted to int due to the integer promotion.
The output from the above code:
-1 is smaller than 0
-1
If I got some concepts mixed up, please let me know. Thanks~
Integer and floating point rank and promotion rules in C and C++
I'd like to take a stab at this to summarize the rules so I can quickly reference them. I've fully studied the question and both of the other two answers here, including the main one by #Lundin. If you want more examples beyond the ones below, go study that answer in detail as well, while referencing my "rules" and "promotion flow" summaries below.
I've also written my own example and demo code here: integer_promotion_overflow_underflow_undefined_behavior.c.
Despite normally being incredibly verbose myself, I'm going to try to keep this a short summary, since the other two answers plus my test code already have sufficient detail via their necessary verbosity.
Integer and variable promotion quick reference guide and summary
3 simple rules
For any operation where multiple operands (input variables) are involved (ex: mathematical operations, comparisons, or ternary), the variables are promoted as required to the required variable type before the operation is performed.
Therefore, you must manually, explicitly cast the output to any desired type you desire if you do not want it to be implicitly chosen for you. See the example below.
All types smaller than int (int32_t on my 64-bit Linux system) are "small types". They cannot be used in ANY operation. So, if all input variables are "small types", they are ALL first promoted to int (int32_t on my 64-bit Linux system) before performing the operation.
Otherwise, if at least one of the input types is int or larger, the other, smaller input type or types are promoted to this largest-input-type's type.
Example
Example: with this code:
uint8_t x = 0;
uint8_t y = 1;
...if you do x - y, they first get implicitly promoted to int (which is int32_t on my 64-bit
system), and you end up with this: (int)x - (int)y, which results in an int type with value
-1, rather than a uint8_t type of value 255. To get the desired 255 result, manually
cast the result back to uint8_t, by doing this: (uint8_t)(x - y).
Promotion flow
The promotion rules are as follows. Promotion from smallest to largest types is as follows.
Read "-->" as "gets promoted to".
The types in square brackets (ex: [int8_t]) are the typical "fixed-width integer types" for the given standard type on a typical 64-bit Unix (Linux or Mac) architecture. See, for example:
https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
And even better, test it for yourself on your machine by running my code here!: stdint_sizes.c from my eRCaGuy_hello_world repo.
1. For integer types
Note: "small types" = bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t].
SMALL TYPES: bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t]
--> int [int32_t]
--> unsigned int [uint32_t]
--> long int [int64_t]
--> unsigned long int [uint64_t]
--> long long int [int64_t]
--> unsigned long long int [uint64_t]
Pointers (ex: void*) and size_t are both 64-bits, so I imagine they fit into the uint64_t category above.
2. For floating point types
float [32-bits] --> double [64-bits] --> long double [128-bits]
I would like to add two clarifications to #Lundin's otherwise excellent answer, regarding example 1, where there are two operands of identical integer type, but are "small types" that require integer promotion.
I'm using the N1256 draft since I don't have access to a paid copy of the C standard.
First: (normative)
6.3.1.1's definition of integer promotion isn't the triggering clause of actually doing integer promotion. In reality it is 6.3.1.8 Usual arithmetic conversions.
Most of the time, the "usual arithmetic conversions" apply when the operands are of different types, in which case at least one operand must be promoted. But the catch is that for integer types, integer promotion is required in all cases.
[clauses of floating-point types come first]
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Second: (non-normative)
There is an explicit example cited by the standard to demonstrate this:
EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the "integer promotions" require that the abstract machine promote the value of each variable to int size
and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
produce the same result, possibly omitting the promotions.

Why is signed and unsigned addition converted differently for 16 and 32 bit integers?

It seems the GCC and Clang interpret addition between a signed and unsigned integers differently, depending on their size. Why is this, and is the conversion consistent on all compilers and platforms?
Take this example:
#include <cstdint>
#include <iostream>
int main()
{
std::cout <<"16 bit uint 2 - int 3 = "<<uint16_t(2)+int16_t(-3)<<std::endl;
std::cout <<"32 bit uint 2 - int 3 = "<<uint32_t(2)+int32_t(-3)<<std::endl;
return 0;
}
Result:
$ ./out.exe
16 bit uint 2 - int 3 = -1
32 bit uint 2 - int 3 = 4294967295
In both cases we got -1, but one was interpreted as an unsigned integer and underflowed. I would have expected both to be converted in the same way.
So again, why do the compilers convert these so differently, and is this guaranteed to be consistent? I tested this with g++ 11.1.0, clang 12.0. and g++ 11.2.0 on Arch Linux and Debian, getting the same result.
When you do uint16_t(2)+int16_t(-3), both operands are types that are smaller than int. Because of this, each operand is promoted to an int and signed + signed results in a signed integer and you get the result of -1 stored in that signed integer.
When you do uint32_t(2)+int32_t(-3), since both operands are the size of an int or larger, no promotion happens and now you are in a case where you have unsigned + signed which results in a conversion of the signed integer into an unsigned integer, and the unsigned value of -1 wraps to being the largest value representable.
So again, why do the compilers convert these so differently,
Standard quotes for [language-lawyer]:
[expr.arith.conv]
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, the integral promotions ([conv.prom]) shall be performed on both operands.
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.
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.
[conv.prom]
A prvalue of an integer type other than bool, char8_­t, char16_­t, char32_­t, or wchar_­t whose integer conversion rank ([conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
These conversions are called integral promotions.
std::uint16_t type may have a lower conversion rank than int in which case it will be promoted when used as an operand. int may be able to represent all values of std::uint16_t in which case the promotion will be to int. The common type of two int is int.
std::uint32_t type may have the same or a higher conversion rank than int in which case it won't be promoted. The common type of an unsigned type and a signed of same rank is an unsigned type.
For an explanation why this conversion behaviour was chosen, see chapter "6.3.1.1 Booleans, characters, and integers" of "
Rationale for
International Standard—
Programming Languages—
C". I won't quote the entire chapter here.
is this guaranteed to be consistent?
The consistency depends on relative sizes of the integer types which are implementation defined.
Why is this,
C (and hence C++) has a rule that effectively says when a type smaller than int is used in an expression it is first promoted to int (the actual rule is a little more complex than that to allow for multiple distinct types of the same size).
Section 6.3.1.1 of the Rationale for International Standard Programming Languages C claims that in early C compilers there were two versions of the promotion rule. "unsigned preserving" and "value preserving" and talks about why they chose the "value preserving" option. To summarise they believed it would produce correct results in a greater proportion of situations.
It does not however explain why the concept of promotion exists in the first place. I would speculate that it existed because on many processors, including the PDP-11 for which C was originally designed, arithmetic operations only operated on words, not on units smaller than words. So it was simpler and more efficient to convert everything smaller than a word to a word at the start of an expression.
On most platforms today int is 32 bits. So both uint16_t and int16_t are promoted to int. The artithmetic proceeds to produce a result of type int with a value of -1.
OTOH uint32_t and int32_t are not smaller than int, so they retain their original size and signedness through the promotion step. The rules for when the operands to an arithmetic operator are of different types come into play and since the operands are the same size the signed operand is converted to unsigned.
The rationale does not seem to talk about this rule, which suggests it goes back to pre-standard C.
and is the conversion consistent on all compilers and platforms?
On an Ansi C or ISO C++ platform it depends on the size of int. With 16 bit int both examples would give large positive values. With 64-bit int both examples would give -1.
On pre-standard implementations it's possible that both expressions might return large positive numbers.
A 16-bit unsigned int can be promoted to a 32-bit int without any lost values due to range differences, so that's what happens. Not so for the 32-bit integers.

Substracting long int and unsigned int [duplicate]

This post is meant to be used as a FAQ regarding implicit integer promotion in C, particularly implicit promotion caused by the usual arithmetic conversions and/or the integer promotions.
Example 1)
Why does this give a strange, large integer number and not 255?
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
Example 2)
Why does this give "-1 is larger than 0"?
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
Example 3)
Why does changing the type in the above example to short fix the problem?
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
puts("-1 is larger than 0"); // will not print
(These examples were intended for a 32 or 64 bit computer with 16 bit short.)
C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.
The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.
Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.
Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.
Integer types and conversion rank
The integer types in C are char, short, int, long, long long and enum.
_Bool/bool is also treated as an integer type when it comes to type promotions.
All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:
Every integer type has an integer conversion rank defined as follows:
— No two signed integer types shall have the same rank, even if they have the same representation.
— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
— The rank of char shall equal the rank of signed char and unsigned char.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.
Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):
The following may be used in an expression wherever an int or unsigned int may
be used:
— An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.
The integer promotions
Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.
Formally, the rule says (C11 6.3.1.1):
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.
This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.
This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!
The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.
This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.
This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.
With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.
The usual arithmetic conversions
Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual artihmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:
(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )
6.3.1.8 Usual arithmetic conversions
Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the usual arithmetic conversions:
First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.
This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.
The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.
According to the previous post, I want to give more information about each example.
Example 1)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since unsigned char is smaller than int, we apply the integer promotion on them, then we have (int)x-(int)y = (int)(-1) and unsigned int (-1) = 4294967295.
The output from the above code:(same as what we expected)
4294967295
-1
How to fix it?
I tried what the previous post recommended, but it doesn't really work.
Here is the code based on the previous post:
change one of them to unsigned int
int main(){
unsigned int x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since x is already an unsigned integer, we only apply the integer promotion to y. Then we get (unsigned int)x-(int)y. Since they still don't have the same type, we apply the usual arithmetic converions, we get (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
Similarly, the following code gets the same result:
int main(){
unsigned char x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
change both of them to unsigned int
int main(){
unsigned int x = 0;
unsigned int y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
}
Since both of them are unsigned int, no integer promotion is needed. By the usual arithmetic converison(have the same type), (unsigned int)x-(unsigned int)y = 4294967295.
The output from the above code:(same as what we expected):
4294967295
-1
One of possible ways to fix the code:(add a type cast in the end)
int main(){
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
printf("%d\n", x - y);
unsigned char z = x-y;
printf("%u\n", z);
}
The output from the above code:
4294967295
-1
255
Example 2)
int main(){
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
printf("%u\n", a+b);
}
Since both of them are integers, no integer promotion is needed. By the usual arithmetic conversion, we get (unsigned int)a+(unsigned int)b = 1+4294967294 = 4294967295.
The output from the above code:(same as what we expected)
-1 is larger than 0
4294967295
How to fix it?
int main(){
unsigned int a = 1;
signed int b = -2;
signed int c = a+b;
if(c < 0)
puts("-1 is smaller than 0");
printf("%d\n", c);
}
The output from the above code:
-1 is smaller than 0
-1
Example 3)
int main(){
unsigned short a = 1;
signed short b = -2;
if(a + b < 0)
puts("-1 is smaller than 0");
printf("%d\n", a+b);
}
The last example fixed the problem since a and b both converted to int due to the integer promotion.
The output from the above code:
-1 is smaller than 0
-1
If I got some concepts mixed up, please let me know. Thanks~
Integer and floating point rank and promotion rules in C and C++
I'd like to take a stab at this to summarize the rules so I can quickly reference them. I've fully studied the question and both of the other two answers here, including the main one by #Lundin. If you want more examples beyond the ones below, go study that answer in detail as well, while referencing my "rules" and "promotion flow" summaries below.
I've also written my own example and demo code here: integer_promotion_overflow_underflow_undefined_behavior.c.
Despite normally being incredibly verbose myself, I'm going to try to keep this a short summary, since the other two answers plus my test code already have sufficient detail via their necessary verbosity.
Integer and variable promotion quick reference guide and summary
3 simple rules
For any operation where multiple operands (input variables) are involved (ex: mathematical operations, comparisons, or ternary), the variables are promoted as required to the required variable type before the operation is performed.
Therefore, you must manually, explicitly cast the output to any desired type you desire if you do not want it to be implicitly chosen for you. See the example below.
All types smaller than int (int32_t on my 64-bit Linux system) are "small types". They cannot be used in ANY operation. So, if all input variables are "small types", they are ALL first promoted to int (int32_t on my 64-bit Linux system) before performing the operation.
Otherwise, if at least one of the input types is int or larger, the other, smaller input type or types are promoted to this largest-input-type's type.
Example
Example: with this code:
uint8_t x = 0;
uint8_t y = 1;
...if you do x - y, they first get implicitly promoted to int (which is int32_t on my 64-bit
system), and you end up with this: (int)x - (int)y, which results in an int type with value
-1, rather than a uint8_t type of value 255. To get the desired 255 result, manually
cast the result back to uint8_t, by doing this: (uint8_t)(x - y).
Promotion flow
The promotion rules are as follows. Promotion from smallest to largest types is as follows.
Read "-->" as "gets promoted to".
The types in square brackets (ex: [int8_t]) are the typical "fixed-width integer types" for the given standard type on a typical 64-bit Unix (Linux or Mac) architecture. See, for example:
https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
And even better, test it for yourself on your machine by running my code here!: stdint_sizes.c from my eRCaGuy_hello_world repo.
1. For integer types
Note: "small types" = bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t].
SMALL TYPES: bool (_Bool), char [int8_t], unsigned char [uint8_t], short [int16_t], unsigned short [uint16_t]
--> int [int32_t]
--> unsigned int [uint32_t]
--> long int [int64_t]
--> unsigned long int [uint64_t]
--> long long int [int64_t]
--> unsigned long long int [uint64_t]
Pointers (ex: void*) and size_t are both 64-bits, so I imagine they fit into the uint64_t category above.
2. For floating point types
float [32-bits] --> double [64-bits] --> long double [128-bits]
I would like to add two clarifications to #Lundin's otherwise excellent answer, regarding example 1, where there are two operands of identical integer type, but are "small types" that require integer promotion.
I'm using the N1256 draft since I don't have access to a paid copy of the C standard.
First: (normative)
6.3.1.1's definition of integer promotion isn't the triggering clause of actually doing integer promotion. In reality it is 6.3.1.8 Usual arithmetic conversions.
Most of the time, the "usual arithmetic conversions" apply when the operands are of different types, in which case at least one operand must be promoted. But the catch is that for integer types, integer promotion is required in all cases.
[clauses of floating-point types come first]
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then 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 is
converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is 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, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.
Second: (non-normative)
There is an explicit example cited by the standard to demonstrate this:
EXAMPLE 2 In executing the fragment
char c1, c2;
/* ... */
c1 = c1 + c2;
the "integer promotions" require that the abstract machine promote the value of each variable to int size
and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
produce the same result, possibly omitting the promotions.

unsigned int / signed int / long long: inexplicable output

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
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).
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.
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.
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"
....

How do promotion rules work when the signedness on either side of a binary operator differ? [duplicate]

This question already has answers here:
Implicit type conversion rules in C++ operators
(9 answers)
Closed 4 years ago.
Consider the following programs:
// http://ideone.com/4I0dT
#include <limits>
#include <iostream>
int main()
{
int max = std::numeric_limits<int>::max();
unsigned int one = 1;
unsigned int result = max + one;
std::cout << result;
}
and
// http://ideone.com/UBuFZ
#include <limits>
#include <iostream>
int main()
{
unsigned int us = 42;
int neg = -43;
int result = us + neg;
std::cout << result;
}
How does the + operator "know" which is the correct type to return? The general rule is to convert all of the arguments to the widest type, but here there's no clear "winner" between int and unsigned int. In the first case, unsigned int must be being chosen as the result of operator+, because I get a result of 2147483648. In the second case, it must be choosing int, because I get a result of -1. Yet I don't see in the general case how this is decidable. Is this undefined behavior I'm seeing or something else?
This is outlined explicitly in §5/9:
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:
If either operand is of type long double, the other shall be converted to long double.
Otherwise, if either operand is double, the other shall be converted to double.
Otherwise, if either operand is float, the other shall be converted to float.
Otherwise, the integral promotions shall be performed on both operands.
Then, if either operand is unsigned long the other shall be converted to unsigned long.
Otherwise, if one operand is a long int and the other unsigned int, then if a long int can represent all the values of an unsigned int, the unsigned int shall be converted to a long int; otherwise both operands shall be converted to unsigned long int.
Otherwise, if either operand is long, the other shall be converted to long.
Otherwise, if either operand is unsigned, the other shall be converted to unsigned.
[Note: otherwise, the only remaining case is that both operands are int]
In both of your scenarios, the result of operator+ is unsigned. Consequently, the second scenario is effectively:
int result = static_cast<int>(us + static_cast<unsigned>(neg));
Because in this case the value of us + neg is not representable by int, the value of result is implementation-defined – §4.7/3:
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.
Before C was standardized, there were differences between compilers -- some followed "value preserving" rules, and others "sign preserving" rules. Sign preserving meant that if either operand was unsigned, the result was unsigned. This was simple, but at times gave rather surprising results (especially when a negative number was converted to an unsigned).
C standardized on the rather more complex "value preserving" rules. Under the value preserving rules, promotion can/does depend on the actual ranges of the types, so you can get different results on different compilers. For example, on most MS-DOS compilers, int is the same size as short and long is different from either. On many current systems int is the same size as long, and short is different from either. With value preserving rules, these can lead to the promoted type being different between the two.
The basic idea of value preserving rules is that it'll promote to a larger signed type if that can represent all the values of the smaller type. For example, a 16-bit unsigned short can be promoted to a 32-bit signed int, because every possible value of unsigned short can be represented as a signed int. The types will be promoted to an unsigned type if and only if that's necessary to preserve the values of the smaller type (e.g., if unsigned short and signed int are both 16 bits, then a signed int can't represent all possible values of unsigned short, so an unsigned short will be promoted to unsigned int).
When you assign the result as you have, the result will get converted to the destination type anyway, so most of this makes relatively little difference -- at least in most typical cases, where it'll just copy the bits into the result, and it's up to you to decide whether to interpret that as signed or unsigned.
When you don't assign the result such as in a comparison, things can get pretty ugly though. For example:
unsigned int a = 5;
signed int b = -5;
if (a > b)
printf("Of course");
else
printf("What!");
Under sign preserving rules, b would be promoted to unsigned, and in the process become equal to UINT_MAX - 4, so the "What!" leg of the if would be taken. With value preserving rules, you can manage to produce some strange results a bit like this as well, but 1) primarily on the DOS-like systems where int is the same size as short, and 2) it's generally harder to do it anyway.
It's choosing whatever type you put your result into or at least cout is honoring that type during output.
I don't remember for sure but I think C++ compilers generate the same arithmetic code for both, it's only compares and output that care about sign.