Question
I have a program with two versions: one using unsigned integers and the other using unsigned long.
As I need to compute the difference between 2 unsigned numbers, I store it in a long int
. An inconsistency appears: the substraction of two unsigned long
works well but the substraction of unsigned int
"wraps around". Moreover, this does not happen if we store the difference in a standard int
.
Here is a minimal example in c++ (but it probably happens in c too):
unsigned long ul3 = 3;
unsigned long ul4 = 4;
unsigned u3 = 3;
unsigned u4 = 4;
long int li;
li = ul3 - ul4; cout << li << endl; // case a: prints -1
li = u3 - u4; cout << li << endl; // case b: prints 4294967295, which is 2^32-1
int i = u3 - u4;
i = ul3 - ul4; cout << i << endl; // case c: prints -1
i = u3 - u4; cout << i << endl; // case d: prints -1
Do you know what the reason is? And how can I reliably fix it?
A solution is to cast u4
as a long int
before substracting it: li = u3 - ((long int) u4);
, but it seems like an unreliable fix.
Note that I am aware of the modularity of unsigned numbers (Is unsigned integer subtraction defined behavior?), but it does not seem to explain the above problem.
Short answer
Case b has a combination of types that follows a specific rule, which makes the wrap-around of unsigned substraction persist in the signed variable. The other cases follow different rules.
Answer
The answer follows from the complicated Implicit type promotion rules .
For any operation between two integers (that are not smaller than standard int
):
If both operands have the same type, then no further conversion is needed.
In the -
operation, the initial unsigned type is kept and wraps around, leading to a value of 2^32-1 (cases b,d) or 2^64-1 (cases a,c).
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.
Case a: In the =
operation, the ranks of types long int
and unsigned long
are equal, so (ul3-ul4)
, which is 2^64-1 is stored in li
as an unsigned long
. When we print, this number is bigger than 2^63-1 (limit of long int
), so it is considered as a negative number. Hence the output of -1.
Case d: In the =
operation, the rank of type unsigned long
is greater than the rank of int
so (u3-u4)
, which is 2^32-1 is stored in i
as an unsigned long
. When we print, this number is bigger than 2^31-1 (limit of int
), so it is considered as a negative number. Hence the output of -1.
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.
Case b: In the =
operation, type long int
can represent all the values of type unsigned long
, so (u3-u4)
, which is 2^32-1 is converted to long int
. Hence the output of 2^32-1.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
Case c: In the =
operation, int
cannot represent all unsigned long
, so (ul3-ul4)
, which is 2^64-1 is stored in i
as an unsigned long
. When we print, the same thing happens as in cases a and d. But why does it also work with an int?