Multiplication / Division format when using <stdint.h>

468 Views Asked by At

I would like to understand how work multiplication and division using <stdint.h> (uint16_t, int16_t, etc).

For example, I guess that the followings produce an uint16_t:

uint16_t * uint16_t 
uint16_t / uint16_t

So there is a risk of overflow.

What happens in the following case ? :

uint32_t res = uint16_t a * uint16_t b

Does the product give an uint16_t result that is then promoted to an uint32_t ? In different words, is it mandatory in this case to cast the two uint16_t operands to two uint32_t before doing the multiplication?

For a division, there is no risk of overflow, hence no need to worry about this case.

Also, what happens when we try to multiply an uint16_t by an int16_t without explicit casting? Does the compiler performs some implicit casting (for example, casting the uint16_t to an int16_t and returning a result as an int16_t) ?

Let me know if my questions are unclear. Thanks.

2

There are 2 best solutions below

0
On BEST ANSWER

C 2018 6.5.5 specifies how the multiplication and divisor operators behave. Paragraph 3 says:

The usual arithmetic conversions are performed on the operands.

6.3.1.8 1 specifies 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 [complex or real], 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.

Rank has a technical definition that largely corresponds to width (number of bits in an integer type).

What happens in the following case ? :

uint32_t res = uint16_t a * uint16_t b

In typical modern C implementations, int is 32 bits, so it can represent all the values of uint16_t. So a and b are promoted to int. Then no further conversion is needed. The multiplication is performed in the int type. This can overflow. For example, if a and b are both 50,000, then the product would be 2,500,000,000, but the largest value a 32-bit int can represent is 2,147,483,647. The C standard does not define the behavior when overflow occurs. To avoid this, you should convert one or both operands to a sufficiently wide type, as with uint32_t res = (uint32_t) a * b;.

Supposing that uint32_t is unsigned int, the integer promotions will leave (uint32_t) a as unsigned int. b will be promoted to int as before, but then the usual arithmetic conversions will convert it to unsigned int, per the third bullet item, and the arithmetic will be performed with unsigned int, and there will be no overflow.

The above assumed int can represent all the values of a uint16_t. If it cannot, then the operands will not be promoted by the integer promotions; they will be left as the uint16_t type. The multiplication will be performed in the uint16_t type. If the result cannot be represented, information will be lost. This is not overflow, because the C standard defines arithmetic in unsigned integer types to wrap modulo 2N, where N is the number of bits in the type. So the behavior is defined, but it will not give the normal mathematical result.

To get the desired result, convert one or both operands to a sufficiently wide type, as with the previous case: uint32_t res = (uint32_t) a * b;.

… is it mandatory in this case to cast the two uint16_t operands to two uint32_t before doing the multiplication?

The C standard does not require this.

Some coding standards require this.

Some compilers may warn you in some circumstances where they can detect an issue.

Getting correct answers requires you to cast where the desired mathematical answer is not representable in the type that results from the usual arithmetic conversions. In any code where you cannot prove that the usual arithmetic conversions produce a type that can represent all desired answers, you should include a cast to a type that can.

Also, what happens when we try to multiply an uint16_t by an int16_t without explicit casting?

In the typical case today, with 32-bit int, both operands are promoted to int, and the arithmetic is performed with int. Once again, you should cast one or both operands to a type sufficiently wide for the desired result.

With a 16-bit int, neither operand is promoted. Then the usual arithmetic conversions convert them both to uint16_t, per the third bullet item, and the multiplication is performed using uint16_t.

Does the compiler performs some implicit casting (for example, casting the uint16_t to an int16_t and returning a result as an int16_t) ?

There is an implicit conversion to uint16_t, as described above. (This is not a cast. The word “cast” is used for an explicit operator in source code, of the form, (type). Because it is an explicit operator, a cast is never implicit. Casts perform conversions. Other conversions can be implicit.)

In a * b, where a is uint16_t and b is int16_t, there is no implicit conversion to int16_t. There will be a conversion or conversions to uint16_t or int, depending on the width of int. If you assign the result to an int16_t object, the assignment will induce a conversion to int16_t. If the value to be assigned cannot be represented in int16_t, the result (which may be a signal) is implementation-defined.

3
On

There are... cases. Operands of * undergo integer promotions, see https://en.cppreference.com/w/c/language/conversion .

Does the product give an uint16_t result

Let's assume a sane, common system - int has 32-bit. On such system int can hold any value of uint16_t. Both operands are promoted to int before doing anything.

On a system with 32-bit int: no, they give an int result.

that is then promoted to an uint32_t ?

Yes. The result is converted to an uint32_t.

From https://en.cppreference.com/w/c/language/operator_assignment :

Assignment performs implicit conversion from the value of rhs to the type of lhs and then replaces the value in the object designated by lhs with the converted value of rhs.

How is it converted? From https://en.cppreference.com/w/c/language/conversion :

if the target type is unsigned, the value 2^b , where b is the number of bits in the target type, is repeatedly subtracted or added to the source value until the result fits in the target type. In other words, unsigned integers implement modulo arithmetic.

The result of uint16_t * uint16_t has to be in range of uint32_t - so there is nothing will happen.

is it mandatory in this case to cast the two uint16_t operands to two uint32_t before doing the multiplication?

No, it's not mandatory. C is a programming language with implicit conversions and promotions, they are done implicitly.

There are "software development guidelines" that "enhance" C for security reasons and require explicitly casting, most notably MISRA.

what happens when we try to multiply an uint16_t by an int16_t without explicit casting?

They both get promoted to an int.

But let's consider something interesting - int can have 16-bits, like on xc8 compiler. On such systems, the types do not get promoted and stay - int16_t and uint16_t. Then, from https://en.cppreference.com/w/c/language/conversion :

If the unsigned type has conversion rank greater than or equal to the rank of the signed type, then the operand with the signed type is implicitly converted to the unsigned type.

int16_t and uint16_t have the same conversion rank - unsinged and signed same types have equal ranks. So, the int16_t will be converted to uint16_t and then it becomes uint16_t * uint16_t.

int16_t will be converted by "repeatedly subtracting or adding" the value of 2^16 = 65536 until it in the range of uint16_t. So, for example, (uint16_t)-5 is equal to 65531, because -5 + 65536 = 65531.

Does the compiler performs some implicit casting

Yes!