Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the order of multiplication variables of different data type cause different results?

Lets say I have 3 variables: a long, an int and a short.

long  l; 
int   i;
short s;
long  lsum;

If this is a pure math, since multiplication has a commutative property, the order of these variables doesn't matter.

 l * i * s = i * s * l = s * i * l.

Let lsum be the container of the multiplication of these 3 variables.

In C, would there be a case where a particular order of these variables cause different result?

If there is a case where the order does matter, not necessarily from this example, what would that be?

like image 999
Nguai al Avatar asked Jan 05 '23 00:01

Nguai al


1 Answers

The order does matter due to integer promotions.

When applying an arithmetic operator, each of its operands is first promoted to int if its rank is less than int (such as char or short). If one of those operands then has a higher rank still (such as long), than the smaller is promoted.

From section 6.3.1 of the C standard:

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

  • A bit-field of type _Bool, int, signed int, or unsigned int.

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. All other types are unchanged by the integer promotions.

From section 6.3.1.8:

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.

As an example (assuming sizeof(int) is 4 and sizeof(long) is 8):

int i;
short s;
long l, result;

i = 0x10000000;
s = 0x10;
l = 0x10000000;

result = s * i * l;
printf("s * i * l=%lx\n", result);
result = l * i * s;
printf("l * i * s=%lx\n", result);

Output:

s * i * l=0
l * i * s=1000000000000000

In this example, s * i is evaluated first. The value of s is promoted to int, then the two int values are multiplied. At this point an overflow occurs unvoking undefined behavior. The result is then promoted to long and multiplied by l, with the result being of type long.

In the latter case, l * i is evaluated first. The value of i is promoted to long, then the two long values are multiplied and an overflow does not occur. The result is then multiplied by s, which is first promoted to long. Again, the result does not overflow.

In a situation like this, I'd recommend casting the leftmost operand to long so that all other operands are promoted to that type. If you have parenthesized subexpressions you may need to apply a cast there as well, depending on the result you want.

like image 103
dbush Avatar answered Feb 12 '23 13:02

dbush