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.
C 2018 6.5.5 specifies how the multiplication and divisor operators behave. Paragraph 3 says:
6.3.1.8 1 specifies the usual arithmetic conversions:
Rank has a technical definition that largely corresponds to width (number of bits in an integer type).
In typical modern C implementations,
int
is 32 bits, so it can represent all the values ofuint16_t
. Soa
andb
are promoted toint
. Then no further conversion is needed. The multiplication is performed in theint
type. This can overflow. For example, ifa
andb
are both 50,000, then the product would be 2,500,000,000, but the largest value a 32-bitint
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 withuint32_t res = (uint32_t) a * b;
.Supposing that
uint32_t
isunsigned int
, the integer promotions will leave(uint32_t) a
asunsigned int
.b
will be promoted toint
as before, but then the usual arithmetic conversions will convert it tounsigned int
, per the third bullet item, and the arithmetic will be performed withunsigned int
, and there will be no overflow.The above assumed
int
can represent all the values of auint16_t
. If it cannot, then the operands will not be promoted by the integer promotions; they will be left as theuint16_t
type. The multiplication will be performed in theuint16_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;
.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.
In the typical case today, with 32-bit
int
, both operands are promoted toint
, and the arithmetic is performed withint
. 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 touint16_t
, per the third bullet item, and the multiplication is performed usinguint16_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
, wherea
isuint16_t
andb
isint16_t
, there is no implicit conversion toint16_t
. There will be a conversion or conversions touint16_t
orint
, depending on the width ofint
. If you assign the result to anint16_t
object, the assignment will induce a conversion toint16_t
. If the value to be assigned cannot be represented inint16_t
, the result (which may be a signal) is implementation-defined.