String bug with float ovf

float Var;
...
Serial.print(Var); // output="ovf"
Serial.print(String(Var)); // program hangs . total annihilation . WTF
...

arduino 1.6.9

put real code that you compiled and show output of the console...

float var;

void setup() {
  Serial.begin(115200);
  var = 0.12345;
  Serial.println(var);
  Serial.println(var,5);
  Serial.println(String(var)); // BAD IDEA TO USE the String class
}

void loop() {}

this gives me as expected in the console

0.12
0.12345
0.12

so the pb is probably in front of the keyboard and staring at the screen or elsewhere in the code... :slight_smile:

PS/ arduino is now 1.6.13 - better to stay current

J-M-L:
so the pb is probably in front of the keyboard and staring at the screen or elsewhere in the code... :slight_smile:

Not quite true. I can't test but op clearly indicates that the issue happens with a possibly uninitialised variable.

Test_1

void setup()
{
  Serial.begin(115200);
  Serial.println("setup()");
  union UnionOvf
  {
    float VarFl;
    uint8_t VarI[4];
  };
  UnionOvf Var;
  Var.VarI[0] = 0;
  Var.VarI[1] = 0;
  Var.VarI[2] = 122;
  Var.VarI[3] = 255;
  Serial.println(Var.VarFl);
  Serial.println(String(Var.VarFl));
  Serial.println("I'm OK");
}

void loop()
{
  // put your main code here, to run repeatedly:
}

Test_2

void setup()
{
  Serial.begin(115200);
  Serial.println("setup()");
  union UnionOvf
  {
    float VarFl;
    uint8_t VarI[4];
  };
  UnionOvf Var;
  Var.VarI[0] = 0;
  Var.VarI[1] = 0;
  Var.VarI[2] = 122;
  Var.VarI[3] = 255;
  Serial.println(Var.VarFl);
  //Serial.println(String(Var.VarFl));
  Serial.println("I'm OK");
}

void loop()
{
  // put your main code here, to run repeatedly:
}

Output of Test_1
setup(

Output of Test_2
setup()
ovf
I'm OK

Sequence "0 0 122 255" is read from i2c eeprom

What kind of black magic do you try to achieve here?

Both float and String are poor (99,9% of the time) choices on a (8-bit AVR style) Arduino...

Nice try. But. Arduino due is based on 32-bit ARM core microcontroller.

Even then, String is just black magic, and using float because you want a decimal is just wrong…

After having this freaking bug I can not disagree with you. Arduino “String” is just for fun and not for a real code.

Let me guess. Instead of a float you suggest using an integer and a multiplier? :frowning:

Just a fixed point will do in most cases. Simply know where the decimal needs to be. For example with money. Instead of using a float like float floatMoney = 2.56 (which will give you rounding error with large amounts of money :wink: ) you save it as cents like int centsMoney = 256. And when it comes to printing it in the more standard way of whole [insert currency here] you add the point :slight_smile:

floats might make sense if your math requires a large dynamic range (e.ge 4E24 / 3.5e-37, you cant reasonably do this with fixed point/scaled math).
but such ranges are very rare in the small micro/Arduino world, and even if you do use floats internally, using scalars in print..() functions is usually preferable.

From WString.cpp:

String::String(float value, unsigned char decimalPlaces)
{
 init();
 char buf[33];
 *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}

String::String(double value, unsigned char decimalPlaces)
{
 init();
 char buf[33];
 *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}

It does not make sense to assign *this to the return value of dtostrf() when dtostrf() returns a pointer to the 4th argument.

Try:

char floatBuf[33];
Serial.print(dtostrf(Var.VarFl, 4, 2, floatBuf);

and let us know what happens.

That is not a bug, the problem is the values of the four bytes, they can't make a valid float value. Maybe this page will explain why.

What float value did you expect with those bytes ?

Try with these values for example:

Var.VarI[0] = 121;
Var.VarI[1] = 233;
Var.VarI[2] = 246;
Var.VarI[3] = 66;

BTW

union UnionOvf
  {
    float VarFl;
    uint8_t VarI[4];
  };
  UnionOvf Var;
  Var.VarI[0] = 0;
  Var.VarI[1] = 0;
  Var.VarI[2] = 122;
  Var.VarI[3] = 255;
  Serial.println(Var.VarFl);

is fully documented as being non deterministic in C++.

if you use VarI to initialize the union, then you can only read from VarI not varFl.

Test_3

#include "avr/dtostrf.h"

void setup()
{
  Serial.begin(115200);
  Serial.println("setup()");
  union UnionOvf
  {
    float VarFl;
    uint8_t VarI[4];
  };
  UnionOvf Var;
  Var.VarI[0] = 0;
  Var.VarI[1] = 0;
  Var.VarI[2] = 122;
  Var.VarI[3] = 255;
  char floatBuf[33];
  Serial.print(dtostrf(Var.VarFl, 4, 2, floatBuf));
  Serial.println("I'm OK");
}

void loop()
{
  // put your main code here, to run repeatedly:
}

Output of Test_3
setup(

Yes, I can understand that this is not a valid float. But after an error with i2c eeprom WR/RD I've found this bug. It is not normal behavior of String object to cause program hanging after taking overflown float as an argument. Normal is to print something like "NaN" or "ovf".

if you use VarI to initialize the union, then you can only read from VarI not varFl.

Why? The whole purpose of a union is to have one chunk of memory that can contain any number of bytes, and to be able to refer to that memory as any of the unioned types.

It is not normal behavior of String object to cause program hanging after taking overflown float as an argument. Normal is to print something like "NaN" or "ovf".

The String class reasonably assumes that dtostrf() will always produce valid results. The problem is NOT the String class. The problem is the user of the String class assuming that it will be able to handle any nonsense thrown its way.

You have CLEARLY demonstrated that the issue is with using the pointer returned by dtostrf(). So, quit harping on the bug in the String class.

It you were smart, you wouldn't be using it at all.

qazx:
Yes, I can understand that this is not a valid float. But after an error with i2c eeprom WR/RD I've found this bug. It is not normal behavior of String object to cause program hanging after taking overflown float as an argument. Normal is to print something like "NaN" or "ovf".

Sorry I didn't see the problem in the String output, I though you were saying "ovf" output was the bug. You are right, there is a bug somewhere. One more reason to not use the String class :slight_smile:

You are right, there is a bug somewhere.

The String class expects certain behavior from the dtostrf() function that is not realistic.

The dtostrf() function calls dtoa_prf(), and then returns a pointer to the input string. That same pointer was passed to dtoa_prf(), which may have set it to NULL when it could not convert the float/double to a string.

Passing invalid data to a function, and expecting the function to always gracefully handle the crappy input is unrealistic. The bug is in the chair.

PaulS:
Why? The whole purpose of a union is to have one chunk of memory that can contain any number of bytes, and to be able to refer to that memory as any of the unioned types.

Harbison and Steele do not say that doing this is implementation specified or undefined, but they do say:

A component of a union should be referenced only if the last assignment to the union was through the same component.

...Standard C makes few guarantees about the layout of unions...

Unions are used in a non-portable fashion any time a union component is referenced when the last assignment to the union was not through the same component. Programmers sometimes do this to "reach under" C's type system and discover something about the computer's underlying data representation (itself a nonportable concept).

PaulS:
Why? The whole purpose of a union is to have one chunk of memory that can contain any number of bytes, and to be able to refer to that memory as any of the unioned types.

No that's not what the C++ standard states Union are for.

The union is only as big as necessary to hold its largest data member. The other data members are allocated in the same bytes as part of that largest member. The details of that allocation are implementation-defined, and it's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

The lifetime of a union member begins when the member is made active. If another member was active previously, its lifetime ends.

In C it is different

If the member used to access the contents of a union is not the same as the member last used to store a value, the object representation of the value that was stored is reinterpreted as an object representation of the new type (this is known as type punning). If the size of the new type is larger than the size of the last-written type, the contents of the excess bytes are unspecified (and may be a trap representation)

while it is safe with our compiler to assume it will do the right thing, it's good to keep that in mind when you use such technics