cfixed-size-types

Multiplication / Division format when using <stdint.h>


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.


Solution

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