2

I have a really simple snippet of code wherein the same calculation outputs different values depending on how I do the calculation. The platform that I am running this code on is an Arduino Uno - with an Atmel MEGA328P microcontroller chip. Here's the code:

void setup() {
  
  Serial.begin(9600);
  unsigned long num = 100927;
  Serial.println(1000*60);
  Serial.println(num/(1000*60));
  Serial.println(num/(60000));
}

void loop() {}

This code prints the following on the console:

-> -5536
-> 0
-> 1

I don't understand why num/(1000*60) and num/(60000) evaluate to different values? Also, 1000*60 evaluates to a garbage value which looks like it somehow set the data type to int and resulted in overflowing the "int" range.

My programming background is heavy in Python. I figure I'm used to python dynamically interpreting variable data types for me which is why the arduino language confuses me a bit here. Any help is appreciated!

0

1 Answer 1

3

This is a pure C++ programming issue, which I cover on my web site.

However, since link-only answers are not allowed here, I reproduce that post below:


On the Arduino (Uno) platform, what do you think will be printed here?

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  
  Serial.println (30000 + 30000); // twice 30000
  Serial.println (60 * 60 * 24);  // seconds in a day
  Serial.println (50 / 100 * 1000); // half of 1000
  }  // end of setup

void loop () { }

Did you guess:

60000
86400
500

Nope!

It prints:

-5536
20864
0

This is because of integer arithmetic. If the compiler can, it treats an numeric literal (like 60) as an int type, which means it has the range -32768 to +32767. This may be unexpected for users of modern compilers because nowadays an int is usually 32 bits. However on the 8-bit Arduino processors such as the Atmega328P the compiler treats an int as 16 bits wide. The C++ standard permits an int size of at least 16 bits, but you are not guaranteed that it will be larger.

And, arithmetic is done using the type of the largest argument, which means the arithmetic in each case is done as 16-bit arithmetic, and thus it overflows once it reaches 32767.

For example, 30000 + 30000 = 60000 which is 0xEA60 in hex. Unfortunately, 0xEA60 is exactly how -5536 is stored in an int type, which is why it prints -5536.

Meanwhile, 60 * 60 * 24 = 86400 which is 0x15180 in hex. As that doesn't fit in 16 bits, it is truncated to 0x5180 which is 20864 in decimal (as printed).

Finally, in integer arithmetic 50/100 is zero, multiply zero by 1000 and you still get zero, which is why the final result is zero.


So, can we "help" the compiler by telling it the sort of result we want, like this?

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  long a = 30000 + 30000;
  long b = 60 * 60 * 24;
  float c = 50 / 100 * 1000;
  
  Serial.println (a);
  Serial.println (b);
  Serial.println (c);
  }  // end of setup

void loop () { }

That prints:

-5536
20864
0.00

So no, that hasn't helped.


Solution

First, you can add a suffix to numeric literals (eg. L for long, or UL for unsigned long), and add a decimal place to floats, like this:

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  
  Serial.println (30000L + 30000); // twice 30000
  Serial.println (60L * 60 * 24);  // seconds in a day
  Serial.println (50.0 / 100 * 1000); // half of 1000
  }  // end of setup

void loop () { }

Now we get:

60000
86400
500.00

You only need to help out with the first literal, once the compiler knows we are using longs (or floats) it sticks with them for the expression. *

Or we can "cast" them:

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  
  long a =  (long) 30000 + 30000;
  long b =  (long) 60 * 60 * 24;
  float c = (float) 50 / 100 * 1000;
  
  Serial.println (a);
  Serial.println (b);
  Serial.println (c);
  }  // end of setup

void loop () { }

Results:

60000
86400
500.00

Casting is useful for variables, because you can't just add "L" to the end of a variable name.

An alternative syntax is to use a constructor like this:

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  
  long a =  long (30000) + 30000;
  long b =  long (60) * 60 * 24;
  float c = float (50) / 100 * 1000;
  
  Serial.println (a);
  Serial.println (b);
  Serial.println (c);
  }  // end of setup

void loop () { }

(Same results).


* It's actually somewhat more complex than that as this link explains: Understand integer conversion rules

The compiler "promotes" a value in an expression to match another "higher-ranked" type, under certain circumstances. For example, adding an int and a long will result in the int being promoted to a long (regardless of whether it appears first in the expression or not). However if an int is being added to another int, it will not promote them to a long, even though the result may not fit into an int.


More explanations

I was asked in the comments why 1000 * 60 is not the same as 60000 from the compiler's point of view.

The answer is that a compiler will treat an integer literal as an int (if possible) and an int on this platform is 16 bits.

Thus 1000 (an int) * 60 (another int) are multiplied using 16-bit arithmetic, giving a result that overflows.

However the literal, 60000, cannot be stored as an int and thus the compiler promotes it to long (aka "long int"). Since it has been promoted to a long it is correctly stored, and further operations on 60000 will be done with long arithmetic.

I have seen people code 60000 as 60000L however that is unnecessary. The compiler is not silly enough to make 60000 be stored as -5536, as that is clearly not the intent of the programmer.

15
  • I think the most important point is that sizeof(int) is 2 on the AVR platform, which is different from about every other current platform I know
    – PMF
    Commented Apr 27, 2023 at 5:48
  • Good point. Modern compilers make an int as 32 bits or even 64 bits. However because of space considerations on a microprocessor they chose to make the int be 16 bits wide. See int - Arduino reference
    – Nick Gammon
    Commented Apr 27, 2023 at 6:16
  • 1
    @PMF You seem to be young. I was socialized with CP/M and DOS, even Atari's TOS, and ints had 16 bits for ages. :-D With the dawn of 32 bit system, I found it irritating that an int became as large as a long. Lesson learned by me: Never assume a size, use the types of "stdint.h" or "cstdint" if I need a certain size. Commented Apr 27, 2023 at 8:21
  • @thebusybee That was mostly before my time, yes. But I also have started to use int32_t and it's siblings just to avoid pitfalls. Luckily, for modern languages such as C# and Java, the size of int is fixed in the specification.
    – PMF
    Commented Apr 27, 2023 at 8:32
  • 1
    Here it is - I'll amend my post.
    – Nick Gammon
    Commented Apr 27, 2023 at 22:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.