So i have this line of C code that takes 10-bit ADC value on the ATmega2560 and scales it to be used later by counters. results should be between 44 and 4166 or something like that
uint16_t tcnt = (60 * 16000000)/((100 + adc_latest * 9) * 64 * 36);
I compiled with avr-gcc and send the value of tcnt to the serial to check it .. it was way outside the expected range
after checking my math and parenthesis multiple times i thought i would simplify and re-write it as follow
uint16_t tcnt = (416667) / (100 + adc_latest *9);
this time i got the expected values
can anyone explain this behavior ? is this because there is a very large intermediate value involved ?
>Solution :
I’m guessing you’re working on a 16-bit processor, and that you’ve been bitten by integer overflow.
I’m guessing adc_latest is an int (16 bits), or perhaps a uint16_t.
And the constants 100, 9, 64, and 36 are all small enough to be represented as 16-bit int. So the subexpression
(100 + adc_latest * 9) * 64 * 36
will all be computed using 16-bit arithmetic.
Suppose adc_latest is 9000. Then (100 + adc_latest * 9) * 64 * 36 is 20966400 — but that’s not a number you can represent in 16 bits. So it’ll overflow, with undesired and perhaps undefined results.
In the numerator, though, the constant 16000000 is already too big to be represented in 16 bits, so the compiler will implicitly treat it as a long int.
When you rewrote it as
416667 / (100 + adc_latest * 9)
you eliminated the overflow in the denominator, so everything worked.
If my suppositions here are correct, another fix would be
(60 * 16000000) / ((100 + adc_latest * 9L) * 64 * 36)
The simple change of 9 to 9L will force everything in the denominator to be computed using long arithmetic also, eliminating overflow there and (I predict) resulting in a correct overall result.
This is all a consequence of the way expressions are evaluated "bottom up" in C. Subexpressions get promoted to a larger, common type only when they have to be. So in your original expression, you ended up with
long / int
At that point, the int in the denominator got promoted to long — but by then it was too late; the overflow had already occurred. The compiler did not notice that since the denominator was eventually going to be promoted to long, it might as well evaluate the whole denominator as a long. It did not evaluate the whole denominator as a long, so overflow in the denominator occurred.