When variables rollover and safe millis() IF condition


recently I read this article www.baldengineer.com/arduino-how-do-you-reset-millis.html about how to be safe about generic millis timing

if (millis() - previousMillis >= interval)

as author predict it will rollover after 49 days, so solution should be

if ((unsigned long)(millis() - previousMillis) >= interval)

and he confirmed this with one example, but there are used 8 bit variables. Funny thing is, at mine Atmega328, only the 8 bit variable will overflow, but not 16 and 32 bits variables. Here is my code


uint8_t a=0, b=1;
Serial.println(a-b); // -1
Serial.println((uint8_t)(a-b)); // 255

uint16_t c=0, d=1;
Serial.println(c-d); // 65535
Serial.println((uint16_t)(c-d)); // 65535

uint32_t e=0, f=1;
Serial.println(e-f); // 4294967295
Serial.println((uint32_t)(e-f)); // 4294967295

so please help me understand why only 8 bit integer rollover and if we need use retyping to unsigned long in IF condition in millis() timer.


if (millis() - previousMillis >= interval)Is perfectly safe and will work even if millis() rolls over as long as interval is less than 49 and a bit days and previousMillis is declared as an unsigned long. millis() returns an unsigned long so there is no need to cast what it returns.

I use that exact syntax in an 'atomic' radio clock sketch. It's been running continuously for about 9 months now, so it's 'rolled over' at least once. No problems noticed.

UKHeliBob is right, that's how to use it to prevent the rollover problem. The extra cast is not needed. If you have doubts, make the variable 'interval' also an 'unsigned long', to be sure that the compiler does everything with 'unsigned long'.

In your sketch, the Serial.println() is the problem. That function does change things, and might even change it more with HEX values. I think the uint8_t is cast to a signed integer, and it prints -1. Here is another test sketch : http://playground.arduino.cc/Code/TimingRollover#arithmetic

uint8_t a=0, b=1;
Serial.println(a-b); // -1
Serial.println((uint8_t)(a-b)); // 255

…is not a rollover in the millis sense. A rollover occurs when the value reaches the maximum for the datatype then “rolls over” to zero. To test b needs to be the maximum possible value (or close)…

uint8_t a=0, b=(uint_8)(~0);

In addition, if a and b are smaller than an int they are promoted to int. The expression is essentially…

 (int)a - (int)b

The weirdness happens because of integer promotion. When you perform arithmetic between with char or unsigned char (int8_t and uint8_t are typedefs for these two data types), the result is promoted to a signed int type.

unsigned char - unsigned char = int.

I verified this behavior with my desktop C++ compiler using the std::is_same struct from the STL. This sequence:

	unsigned char a=0,b=1;
	cout << boolalpha << (bool)is_same<int,decltype(a-b)>::value << endl;

prints “true” to stdout.

Because of the promotion, your first Serial call is calling the Serial.println(int) function instead of Serial.println(unsigned char) like you expected, so the number is treated as a signed type and printed like that.

The promotion only affects primitive types whose sizeof is less than sizeof(int). That is why uint16_t and uint32_t behave as expected, but the uint8_t version needed an explicit conversion back to uint8_t.

Although, you are misunderstanding the meaning of “rollover”. Every one of the Serial prints is showing an eample of rollover. The only differences you are seeing is the difference between signed and unsigned rollover.


my main issue was if there is need for some extra code for millis timer, as is written in the article from my first post. I also asked author and his answer (in comments) is:

People who understand the compiler’s rules much better than I have told me it is a good idea. I haven’t proven to myself yet if it is required.

But he made a mistake to confirm this with sample code, where he is using a byte variable as "millis counter" instead of unsigned long. And answer from Jiggy-Ninja was answer to all this:

The promotion only affects primitive types whose sizeof is less than sizeof(int)

so just simple IF statement is needed,

thank you all.

Thanks. It's my code/site.

I prefer to the cast to give some flexibility in the the other variables.

I also find it is helpful for people reading the code to (help) explain what is happening.

Not sure why it all of a sudden became a big issue.

[quote author=James C4S link=msg=2853402 date=1469319732] Not sure why it all of a sudden became a big issue. [/quote]

The cast gives the feeling to someone reading the code that millis() and/or previousMillis are not unsigned long. But they are and they must be. Because they are already unsigned long, the rollover problem is already solved with the subtraction, that is before the cast is done. The outcome of the subtraction is always a (small) positive number, for example 6000 for 6 seconds.

I would prefer not to use that cast, but add a comment that says : "// This subtraction in the if-statement must be done with unsigned long to prevent the rollover problem"

I mean, the cast in this example is really confusing:

int a = 3;
int b = 4;
if ( (int)( a - b ) > -5 )

When I take a quick look at that code, I get stuck at that line and my brains must start to work.

nothing agains you, I just posted your code to one project on github and author respond that for sure that is not needed, so I opened thread here because I want to know how it works (I’m not experienced coder, so I’m always learning).

I believe this is benefical for people, for example I learned that byte variables are retyped to int in IF or print statemens.