I have a question regarding the implicit conversion of uint8_t
and uint16_t
using the Arduino IDE 1.8.2 (gcc 4.9.2.). The hardware is a standard Arduino (ATMega328p).
I wrote a piece of code using uint8_t
and decided afterwards to switch to uint16_t
. (Should have seen that coming...)
However, they seem to behave slightly different for implicit conversions, what caused errors in the program.
A minimum working example is the following:
void setup()
{
uint8_t x = 15;
uint8_t y = 5;
float myF = y-x;
Serial.begin(74880);
Serial.println(myF);
}
This will print -10.00 on my serial console.
That's nice and what I expected.
However, if I change x
(or x
and y
) to uint16_t
the result will be 65526.00!
If I change myF
from float to int I get -10 again. (I never change any of the values)
As I store the result in a signed data type, I assume the compiler realises the possibility of negative values and "handles the situation correctly" (preserves the sign, as it does in the int case) or prints a warning in case it isn't happy about mismatching data types. However, even with the warning level set to "all" it never showed a warning. So I assume the compiler knows how to handle the situation without losing a sign/data.
Also since it works with int as target data type, it is surprising to me that it doesn't work with bigger float.
I have tested the situation on my x86 system - gcc 4.7.3 preserves the sign. However, in the 8-bit microcontroller world of AVR different rules/conditions might apply. (?)
So what is going on there? Maybe someone with more compiler knowledge can help out here..
(I know I could have avoided this situation by explicitly casting, but therefore I would have had to be aware of this pitfall.
So I would like to know what exactly causes it, as it really was a surprise when switching from uint8_t
to uint16_t
..)
I've read that according to the integer conversion rules "integer types smaller than int are promoted to int when an operation is performed on them". I assume avr-gcc follows integer promotions. (?) So I understand the actual calculation typically runs on an int anyway and is then converted to the target data type (float in this case). Is the problem here that the uint16_t
is of equal, but not smaller size than the 16 bit int of AVR and therefore the uint16_t
cannot be promoted? If so, why does it work with an int as the target type?
And why does it work with an int as target variable, but not with a 4-byte float? And why doesn't it warn?
So what is going on there?
If an
int
can represent all values of the original type ..., the value is converted to anint
; otherwise, it is converted to anunsigned int
. These are called the integer promotions.
C11 §6.3.1.1 2
With the below, y-x
promotes each x,y
to int
and computes 5-15 which is int -10
and assigns that values to mF
.
uint8_t x = 15;
uint8_t y = 5;
float myF = y-x;
With the below, y-x
promotes each x,y
to unsigned
and computes 5u-15u which is unsigned 65526u
and assigns that values to mF
.
uint16_t x = 15;
uint16_t y = 5;
float myF = y-x;
Why unsigned
and not int
? A uint16_t
does not meet the "if an int
can represent all values of the original type" condition on a 16-bit int
platform when promoting a uint16_t
.
No mystery, just integer promotions on a platform with a 16-bit int/unsigned