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.
CodePudding user response:
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 islong 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 isdouble
.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 isfloat.
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 twouint32_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 anint16_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 anint16_t
and returning a result as anint16_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.
CodePudding user response:
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!