0

I have discovered a strange error in my Arduino. It's not in the code. I think it's a hardware bug in the ALU of the microcontroller, maybe in clone only. So there is a variable called feedLimit and its value is 30, which doesn't really matter. When I print the answer of feedLimit301000 using this command Serial.println(feedLimit*30*1000) The answer printed on the serial monitor is 30528, which is obviously incorrect. But when I print feedLimit*30000, the answer is correct (300000). The answer should be the same in both cases but it's not. I have attached the code as well as a screenshot of the output. Can somebody please let me know if this is a bug in Arduino or something else? I have never experienced this kind of bug at the hardware level. Thanks.

int interval, interval2;

int feedingTime = 3; //Must feed once every 3 minutes
int feedLimit = 30;  //If fed 10 times or more within feedingTime span, the fiesh dies
int dieAfter = 30;   //Will die if its not fed in 10 minutes
int feedCounter = 0; //Counts how many time fed in feedingTime span

void setup() {
  Serial.begin(9600);
  pinMode(btnPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);

  Serial.println(feedLimit);
  Serial.println(feedLimit*60*1000);
  Serial.println(feedLimit*60000);
}

enter image description here

1
  • Not a bug. You're working with integers on an 8 bit microcontroller. The maximum they can hold is 32767.
    – Majenko
    Commented Jan 5, 2022 at 10:55

1 Answer 1

3

This is not a bug. It's just that you are working with signed 16 bit integers on an 8 bit microcontroller. The maximum an int can store is 32767, and literals are 16 bit signed by default.

Your three prints are:

  Serial.println(feedLimit);

Print 30.

  Serial.println(feedLimit*60*1000);

Multiply feedLimit by 60 = 1800. Multuply 1800 by 1000 = 1800000. Fit 1800000 into 16 bit signed = 30538. Also works the other way: preprocessor multiplies two 16 bit ints to create a new 16 bit int constant: 60 * 1000 = 60000, which is too big to fit in an int and truncates to 27232. Multiply by 30 = 30538 when truncated to 16 bits.1

  Serial.println(feedLimit*60000);

Multiply 30 by 60000. 60000 is too big to store in an int so it is implicitly promoted to a long. 30 * 60000 = 1800000. The result is a long because you have an implicit long in your calculation.

You have to take care with big numbers on small microcontrollers and keep in mind the limits of each variable type, along with what the default data type for literals is.

  • char: -128 to +127
  • unsigned char / byte: 0 to 255
  • int: -32,768 to +32,767
  • unsigned int: 0 to 65,535
  • long: -2,147,483,648 to 2,147,483,647
  • unsigned long: 0 to 4,294,967,295

You can force literals to be a specific data type by adding a suffix to them:

  • L: Long
  • UL: Unsigned long

(There are others too but less often used).

So your first (failed) calculation can be fixed by forcing one of the values to be a long:

  Serial.println(feedLimit*60*1000L);

1: It seems avr-gcc isn't clever enough to do this, instead it's always treating each operation separately, and each time it works from int-to-int using two 8-bit registers to represent each value or result.

9
  • This was very helpful. Thank you :)
    – Z Dhillon
    Commented Jan 5, 2022 at 11:15
  • Alternatively, one can use the types with explicit sizes: int8_t, int16_t, int32_t etc. These will have the expected size regardless of what CPU is used.
    – PMF
    Commented Jan 5, 2022 at 16:10
  • I don't think that a folded constant of 60000 is truncated to 27232. Did you check that in compiled code? Commented Jan 5, 2022 at 22:32
  • @thebusybee It seems avr-gcc isn't clever enough to fold 60 * 1000 as constants into a single 60000 constant. Instead it does individual mul calls at runtime - <variable> * 60 followed by <result of previous> * 1000. And at each stage it's working with just 2 8 bit registers per value (or just 1 in the case of 60 of course, it's not that stupid) and all results go into two registers every time. Everything truncates and truncates again. But it doesn't think to coalesce literals like that though...
    – Majenko
    Commented Jan 6, 2022 at 0:09
  • 1
    @Majenko last night I also had trouble trying to replicate what you saw in folding. But my thoughts on it were more that regardless of what it does, you're asking it to do something undefined and it's hard to have expectations of the optimizer in such cases. I would have tested -fwrapv to check for a change. But, no original problem, no test. As you said not ultimately relevant. About the only thing I thought to add is that the literal's specified base affects whether unsigned types are considered candidates. E.g. 32768 is long, but 0x8000 is unsigned int with 16-bit systems.
    – timemage
    Commented Jan 6, 2022 at 15:57

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.