I came across this in larger program. If I do the calculation with the variable names I get one (wrong) answer. If I do it with the actual values, the result is correct.
#define ENCODER_RESOLUTION 4096
unsigned long statusUpdateInterval;
uint16_t encFactor;
void setup()
{
Serial.begin(115200);
statusUpdateInterval = 100;
encFactor = 60 * 1000 / statusUpdateInterval / ENCODER_RESOLUTION;
Serial.print("result-1 = ");
Serial.println(encFactor);
// use the actual values
encFactor = 60 * 1000 / 100 / 4096;
Serial.print("result-2 = ");
Serial.println(encFactor);
}
void loop()
{
// put your main code here, to run repeatedly:
}
The multiplication in the above line overflows on the Arduino Uno and the like, where the default integer size is 16 bits with maximum positive value 32767.
And of course, if you make the obvious simplification to encFactor = 60 * 10 / 4096;
the integer division result will be zero, so there is no point in doing that bit of math.
Lemme take a guess. The experts will correct me, I hope.
In the second formula, the compiler knows it can evaluate the entire expression at compile time. It does this using 32 bit, or more probably, 64 bit maths because it's running on a PC/laptop.
In the first formula, because of the type-cast, maybe the compiler thinks at least some of the evaluation must be done at run time. So at some point, 16-bit maths is used and an overflow occurs.
sketch_mar22b.ino:2:22: warning: integer overflow in expression [-Woverflow]
int encFactor = 60 * 1000 / 100 / 4096;
~~~^~~~~~
In C++, the evaluation order of the expression
encFactor = 60 * 1000 / 100 / 4096;
follows the standard precedence and associativity rules for arithmetic operators: Multiplication (*) and division (/) have the same precedence and are evaluated from left to right (left-associative).
Breaking it down step by step:
so in theory
60 * 1000 → 60000
60000 / 100 → 600
600 / 4096 → 0
but because you are targeting a platform where an int is 16 bits
so on a UNO
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
while (!Serial) delay(1);
uint16_t encFactor = 60 * 1000 / (unsigned long)100 / 4096;
Serial.println(encFactor);
}
void loop() {}
The compiler warns about the first overflow, but when the program executes on an Arduino Uno R3, the result "10485" is printed, which makes no sense.
Compiler Explorer shows that this is a bug. The machine code for the above, compiled for Arduino 1.8.9 is to load a constant 10485 and call print (comment added)
It does make sense
60 * 1000 → overflow -5536
-5536 is signed, it is promoted to a 32-bit signed int before conversion and on a 32-bit int, -5536 is stored as 0xFFFFEA70 0xFFFFEA60 (sign-extended).. When assigned to unsigned long, because unsigned wins when you divide, the bit pattern remains the same but is interpreted as 4294961760
Now try to see what 4294961760 / 100 / 4096 is…
The 32 bit hex representation of -5536 is actually FFFFEA60, dividing by 4096 is a 12 bit right shift to FFFFE (1048574), integer divide by 100 decimal = 10485
Just a precision : it needs to be in the first evaluated sub-expression (either 60 and/or 1000 here) and all sub-expressions that will be evaluated independently.
➜ Then the first evaluation (in the order of priority) becomes an unsigned long and the rest of the math is implicitly promoted to unsigned long maths.
That is : when you do a * b * c * d the compiler calculates first a * b using the C++ rules of implicit promotion and default type. So you need to get that first part right.
You need to double check also if there are parentheses : in C++, both expressions * a * b * c * d and (a * b) * (c * d) could lead to different results
With a * b * c * d, the multiplication will proceed in the following order:
a * b
Then the result is multiplied by c.
Then the result is multiplied by d.
whereas with (a * b) * (c * d), the expression inside the parentheses is evaluated first:
a * b
c * d
Then the two results are multiplied together.
In theory, while the order of operations might appear different, the result is the same because multiplication is both left-associative and commutative but this is only if there is no overflow in between
➜ (60ul * 1000) * (60 * 1000) ➜ will lead to an unexpected answer as the second expression does overflow before being promoted to unsigned long through the implicit rules.