Help me understand, data types and multiplication

Why is adding the parentheses causing this behavior?

I know they don't do any difference in this case for the mathematical equation just curious why.

uint8_t a = 10;

uint32_t b,c;

b = (uint32_t) 5000*10*a+5000;
c = (uint32_t) (5000*10*a)+5000;

Serial.println(b); // Prints 505000. I expect this.
Serial.println(c); // Prints 4294948008. Not this.

Try the following codes and teach yourself why the right hand operands should have 'explicit' 'castings'.

uint8_t a = 10;

uint32_t b, c;
void setup()
{
  Serial.begin(9600);
  b = (uint32_t) 5000 * (uint32_t)10 * (uint32_t)a + (uint32_t)5000;
  c = (uint32_t) ((uint32_t)5000 * (uint32_t)10 * (uint32_t)a) + (uint32_t)5000;

  Serial.println(b); // Prints 505000. I expect this.
  Serial.println(c); // Prints 505000. [u]Prints 4294948008. Not this.[/u]

}

void loop()
{

}

Thank you!

It is a combination of what is usually called “operator precedence” and so called “the usual arithmetic conversions”.

The usual arithmetic conversions basically say that when you supply two values of the same type to a binary operator, the calculation is performed in the domain of that type. But if you mix values of two different types in a binary operator, the calculation is performed in the domain of the larger type (and unsigned wins over signed).

In C and C++ the cast operator (uint32_t) has very high precedence - higher than binary * operator. This means that the first expression is seen by the compiler as

b = ((uint32_t) 5000) * 10 * a + 5000;

The value 5000 is immediately converted to uint32_t type. This type has range [0, 2<sup>32</sup>) on our platform. According to the usual arithmetic conversions, 5000 is then multiplied by 10 in the domain of uint32_t type, producing 50000. After that it is multiplied by a, which is also 10. The intermediate result is 500000. After that 5000 is added to the intermediate result and the final value of 505000 is stored in b. Note that at all stages the intermediate result remains within the range of uint32_t type.

In the second expression you added parenthesis to change the grouping of operators and operands.

In the second expression the cast is no longer attached to 5000. The second expression begins with a multiplication of 5000 and 10. 5000 has type int, which has range [-32768, 32767] on our platform. You multiply it by 10, which also has type int. This means that the calculations are performed in the domain of type int. The mathematical result is 50000, which is already out of range for int. This is called signed arithmetic overflow. Signed arithmetic overflow triggers undefined behavior. After that the behavior of your expression is undefined and there’s no point in analyzing it further. You can regard that 4294948008 as nonsensical and generally unpredictable value - a garbage produced by undefined behavior.


Note that you don’t need specifically casts to make this expression behave as expected. This

b = 5000ul * 10 * a + 5000;

will also follow the first scenario.

@OP

And you should acknowledge that this: 'Serial.println(c); // Prints 4294948008' is also correct as you have asked for it.

(1) You have asked Arduino UNO to compute this: 50001010.
By default, the 'processing buffer size' of Arduino UNO is '16-bit of type int'. So, the compiler will keep this: 0xA120 (lower 16-bit) of the result of 500010a (0x7A120).

(2) Then you have applied this cast: uint32_t which means that you have asked the compiler to expand the 'processing buffer size to 32-bit of type unsigned' and then keep the value A120 into it. The compiler will happily expand the 'processing buffer size' to 32-bit; A120 will be stored into lower 16-bit and the upper 16-bit will be populated by the sign bit of A120, which is 1 (A = 1010). Now, the temporary result is '1111 1111 1111 1111 A120 = FFFFA120. Now, add 5000 (0x1388) with it, and we are getting: 0xFFFFB4A8 which is an unsigned number as is evident from the data type (uint32_t) of c. Let us execute this command: Serial.println(c, DEC) and observe that the Serial Monitor shows 4294948008.

Thanks for the clearification! Much appriciated.

After reading this and watching the videos below I am beginning to understand. As I think of it, many times before I have encountered this problem but instead changed every variable in the calculation to the "biggest" in this case unint32_t. Now this will help me reduce code size and thats great.

nollieflip:
After reading this and watching the videos below I am beginning to understand.

Lots of incorrect information in those videos. Basically each video form the very first minutes - a stream of completely made up nonsense. They are just catastrophically bad.

Do yourself a favor a get a good book on C or C++. This is always a much better idea than watching garbage-grade videos on YouTube.

Montmorency:
In the second expression the cast is no longer attached to 5000. The second expression begins with a multiplication of 5000 and 10. 5000 has type int, which has range [-32768, 32767] on our platform. You multiply it by 10, which also has type int. This means that the calculations are performed in the domain of type int. The mathematical result is 50000, which is already out of range for int. This is called signed arithmetic overflow. Signed arithmetic overflow triggers undefined behavior. After that the behavior of your expression is undefined and there's no point in analyzing it further. You can regard that 4294948008 as nonsensical and generally unpredictable value - a garbage produced by undefined behavior.

4294948008 is not a nonsensical value at all; it is exactly what the OP has asked for. Please, see Post#4.

GolamMostafa:
4294948008 is not a nonsensical value at all; it is exactly what the OP has asked for. Please, see Post#4.

Yes it is, from the point of view of C++ and C languages. Both languages clearly and explicitly states that the behavior is undefined. This immediately means that the result of that expression (if it even produces any result) is nonsensical. End of story.

In your post #4 you are simply trying to pick apart code generated by some specific compiler with some specific settings under some specific undefined behavior conditions. Your analysis is correct, but it is still a pointless waste of time. Today the value is 4294948008, tomorrow it might become 12345 just because compiler decided to handle it differently.

We already thoroughly covered the topic of undefined behavior to great extent in other threads. I see no reason to do it all over again.