Math problem with variable names

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:
}

Continuing (hit post too soon).

For some reason, the first result is 10485. Not sure where that comes from (overflow?).

If I put the same code in Visual Studio, it gives the correct answer (0) in both cases.

??

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.

1 Like
unsigned long  encFactor;
.  .  .

encFactor = (60 * 1000 * 4096 )/ 100;

Likely because 60 * 1000 does not overflow there whereas it does on a small Arduino

On the Arduino Uno R3, this code prints "0" as expected.

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
while(!Serial) delay(1);
uint16_t   encFactor = 60 * 1000 / 100 / 4096;
Serial.println(encFactor);
}
void loop() {}

With the handy "verbose" flag set in the IDE Preferences, the compiler emits this warning;

C:\Users\Jim\Desktop\sketch_mar22a\sketch_mar22a.ino:5:27: warning: integer overflow in expression [-Woverflow]
 uint16_t   encFactor = 60 * 1000 / 100 / 4096;
                        ~~~^~~~~~
1 Like

Interesting.

uint16_t res = 60 * 1000; // overflows to 10485

And wondering why this overflows:

encFactor = 60 * 1000 / (unsigned long)100 / 4096;

But this doesn't?

encFactor = 60 * 1000 / 100 / 4096;

K

Hmmm. Good question!

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.

It does overflow

if you compile this for a UNO

void setup() {
  int encFactor = 60 * 1000 / 100 / 4096;
	Serial.begin(115200); 
	Serial.println(encFactor);
}

void loop() {}

you get a waring

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

  1. 60 * 100060000
  2. 60000 / 100600
  3. 600 / 40960

but because you are targeting a platform where an int is 16 bits
so on a UNO

  1. 60 * 1000 → overflow -5536
  2. -5536 / 100-55
  3. -55 / 40960

This is indeed a puzzle:

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)

        ldi r20,lo8(10)
        ldi r21,0
        ldi r22,lo8(-11)  //low byte of 10485 (dec)
        ldi r23,lo8(40)  //high byte of 10485 (dec)
        ldi r24,lo8(Serial)
        ldi r25,hi8(Serial)
        jmp Print::println(unsigned int, int)

Whereas, the following code prints "0" as expected:

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  while (!Serial) delay(1);
  uint16_t   encFactor = 60UL * 1000 / 100 / 4096;
  Serial.println(encFactor);
}

void loop() {}

Using IDE 2.3.4 and Nano Every board, I get no overflow warnings.

I would normally leave it on, but lack of word-wrap in the Output window makes it kind of useless.

K

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… :wink:

(Corrected hex value - thx to @jremington )

2 Likes

Thanks, that is what I forgot.

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

1 Like

Oops - my bad you are totally right, it’s 0xFFFFEA60

So much for trying to do that in my head … I got 4294961760 right

The divide by 100 (with truncated result) happens before the 10 bit shift but it leads to the same result indeed

There are warnings in 2.3.4 for a Nano Every with your code from post #1.

C:\Users\werner\AppData\Local\Temp\.arduinoIDE-unsaved2025223-13268-1oj4c29.sxso\sketch_mar23a\sketch_mar23a.ino: In function 'void setup()':
C:\Users\werner\AppData\Local\Temp\.arduinoIDE-unsaved2025223-13268-1oj4c29.sxso\sketch_mar23a\sketch_mar23a.ino:11:18: warning: integer overflow in expression [-Woverflow]
   encFactor = 60 * 1000 / statusUpdateInterval / ENCODER_RESOLUTION;
               ~~~^~~~~~
C:\Users\werner\AppData\Local\Temp\.arduinoIDE-unsaved2025223-13268-1oj4c29.sxso\sketch_mar23a\sketch_mar23a.ino:17:18: warning: integer overflow in expression [-Woverflow]
   encFactor = 60 * 1000 / 100 / 4096;
               ~~~^~~~~~

When you add at least one unsigned long constant in your formulars the warnings disappear:

#define ENCODER_RESOLUTION 4096
unsigned long statusUpdateInterval;
uint16_t encFactor;

void setup() 
{
  Serial.begin(115200);
  
  statusUpdateInterval = 100;

  encFactor = 60 * 1000UL / statusUpdateInterval / ENCODER_RESOLUTION;

  Serial.print("result-1 = ");
  Serial.println(encFactor);

  // use the actual values
  encFactor = 60 * 1000UL / 100 / 4096;

  Serial.print("result-2 = ");
  Serial.println(encFactor);

}

void loop() 
{
  // put your main code here, to run repeatedly:
}

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.

Yes, there are -- now that I turned on warnings :frowning:

I only had verbose enabled.

Thanks !!

K

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.