Search code examples
c++arduinofloating-pointtype-conversionavr-gcc

Implicit conversion to float using avr-gcc: uint8_t vs. uint16_t


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?


Solution

  • So what is going on there?

    Integer promotions

    If an int can represent all values of the original type ..., the value is converted to an int; otherwise, it is converted to an unsigned 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