Int overflow on uint32_t but not on uint16_t

Board, Nano clone on old bootloader, arduino IDE 1.8.10.

I'm trying to get amount of seconds from midnight for my trigger function. I'm having strange behaviour: when using uint16_t I'm not getting any overflow, but when using uint32_t, int32_t, long int I'm getting an overflow. Am I missing something?

currentTime is DateType type from RTClib library and currentTime.hour() == 15.

  uint16_t currentSeconds = 0;

  currentSeconds = currentTime.hour();
  Serial.println(currentSeconds);

  currentSeconds =  currentTime.hour() * 60;
  Serial.println(currentSeconds);

  currentSeconds =  currentTime.hour() * 60 * 60;
  Serial.println(currentSeconds);

Serial output

15
900
54000
  uint32_t currentSeconds = 0;

  currentSeconds = currentTime.hour();
  Serial.println(currentSeconds);

  currentSeconds =  currentTime.hour() * 60;
  Serial.println(currentSeconds);

  currentSeconds =  currentTime.hour() * 60 * 60;
  Serial.println(currentSeconds);

Serial output

15
900
4294955760

Datetime uses these types:

  • uint16_t year,
  • uint8_t month,
  • uint8_t day,
  • uint8_t hour,
  • uint8_t min,
  • uint8_t sec

To get the number of seconds since midnight you need this

uint32_t numberOfSeconds = currentTime.hour()*3600 + currentTime.min()*60 + currentTime.sec();

As this can reach 86400, uint32_t is needed.

The compiler doesn’t look at the variable you want to assign to to determine size. It looks at the things you’re doing math with. Since everything on the right hand side of your equal sign is int or byte it does the math with int, overflows, and then assigns the value to the long.

Rewrite like this to force unsigned long in the calculation.

currentSeconds =  currentTime.hour() * 60ul * 60ul;

Delta_G: The compiler doesn’t look at the variable you want to assign to to determine size. It looks at the things you’re doing math with. Since everything on the right hand side of your equal sign is int or byte it does the math with int, overflows, and then assigns the value to the long.

Rewrite like this to force unsigned long in the calculation.

currentSeconds =  currentTime.hour() * 60ul * 60ul;

Thank you, seems to work. But this doesn't explain why it worked with uint16_t and not with 32, strange behaviour.

pangu:
Thank you, seems to work. But this doesn’t explain why it worked with uint16_t and not with 32, strange behaviour.

1. You have: 156060 = 5400 = 0xD2F0

2. You know the maximum size and polarity of your number and accordingly, you have declared data type as the following which is fine.

uint16_t currentSeconds = 0;

3. Now, as a matter of experiment you have declared the following unsigned long (32-bit) variable to hold your data of Step-1.

uint32_t currentSeconds = 0;

4. Compiler has created the following storage space (Fig-1) for your data; where, the lower 16-bit has been filled up by your data of Step-1.

Figure-1:

5. What will go at the upper 16-bit of Fig-1? There must be going something as you have asked for a 32-bit storage space. The compiler fills it up by the copies of the sign bit (MSBit) of the lower 16-bit of data which is 1. And, accordingly, you have the following variable (Fig-2) in memory with data: FFFFD2F0.

Figure-2:

6. You have executed the following codes to show the value of Fig-2, which is an unsigned value, and you have have nicely got 4294955760-- the decimal value of 0xFFFFD2F0.

uint32_t currentSeconds = 0;
  currentSeconds =  currentTime.hour() * 60 * 60; // (15*60*60 = 54000 = D2F0) ==> FFFFD2F0
  Serial.println(currentSeconds);   //shows: 4294955760

Conclusion: The compiler/MCU has not done any mistake; it has exactly given what you have asked for.

Note: Can you figure out the reason(s) why the codes of Post#2 have worked with your uint32-t data type?

The operands are casted as ul (unsigned long); as a result, the compiler has populated the upper 16-bit of the 32-bit storage space with 0s and not by the copies of the MSbit (1) of the lower 16-bit data (D2F0).

uint32.png

uint32X.png

this doesn't explain why it worked with uint16_t and not with 32

It was an accident of size that the correct value appeared with uint16_t.

You were NOT getting overflow with uint32_t, merely treating a negative 16 bit int as unsigned 32 bit.

jremington: It was an accident of size that the correct value appeared with uint16_t.

Why should it be an accident? It is exactly what it is. The OP has declared an unsigned 16-bit variable into which the result (15*60*60 = 54000 = 0xD2F0) has fitted very well -- the variable is capable to accommodate values up to 65535 (0xFFFF).