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

Z Dhillon
  • 11
  • 2
  • Not a bug. You're working with integers on an 8 bit microcontroller. The maximum they can hold is 32767. – Majenko Jan 05 '22 at 10:55

1 Answers1

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.

Majenko
  • 103,876
  • 5
  • 75
  • 133
  • This was very helpful. Thank you :) – Z Dhillon Jan 05 '22 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 Jan 05 '22 at 16:10
  • I don't think that a folded constant of 60000 is truncated to 27232. Did you check that in compiled code? – the busybee Jan 05 '22 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 - ` * 60` followed by ` * 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 Jan 06 '22 at 00:09
  • Re “_It seems avr-gcc isn't clever enough to fold 60 * 1000 as constants into a single 60000 constant_”: It can't do that: that would break the rules of the language about implicit typing. But it _is_ clever: it notices `feedLimit` doesn't change and folds `feedLimit*60*1000` into 30528. No runtime `mul` calls. – Edgar Bonet Jan 06 '22 at 08:25
  • @EdgarBonet Not according to the output of `avr-gcc -S` in my tests. There was a long list of `mul` instructions to get the result. – Majenko Jan 06 '22 at 10:51
  • Mmm... weird. The avr-g++ 7.3.0 shipped with Arduino 1.8.15 does the optimization. Did you compile with `-Os -flto`? – Edgar Bonet Jan 06 '22 at 12:33
  • @EdgarBonet No, I didn't. I wouldn't expect -flto to have any effect since there's no linking. -Os *might* but who knows? It doesn't really matter anyway, since the exact "why" is irrelevant. Whatever the compiler decides to do or doesn't decide to do, it's still 16 bit integers unless there's a good reason for it to promote to long. – Majenko Jan 06 '22 at 12:40
  • 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 Jan 06 '22 at 15:57