Hey folks,
I'm having some issues reading floats as strings over serial from a third-party device. 99% of the time it works and I get my floats as expected.
But sometimes the incoming string data gets corrupted such that two floats are concatenated into one large one (1234.5678 becomes 12341234.5678 but more digits). The string still passes my basic validation (contains all valid characters) and I use String.toFloat() to convert the string to a float. Attempting to print this value results in "ovf" which I understand is a limitation of the Print function, not necessarily that the float variable itself has overflowed. This makes debugging challenging.
How can I determine if a string will overflow toFloat() or similarly, how can I determine if the float from toFloat has overflowed?
Thanks for the help,
Elliot
ebradbury:
How can I determine if a string will overflow toFloat() or similarly, how can I determine if the float from toFloat has overflowed?
Is that really important, considering you have an input error situation? Shouldn't you fix that first?
A valid float will contain ONLY the following, in the following order:
An optional '-' as the FIRST character only
Some number of digits, '0' through '9'
ONE, and ONLY one, optional decimal point.
Some number of digits, '0' through '9'
If you find ANY characters that are not '-', '.', or '0' through '9', then reject the whole string
If you find a '-', and it is NOT the FIRST character of the string, then reject the whole string
If you find more than one '.', then reject the whole string
A switch statement as you read each character will make this parsing trivial.
Regards,
Ray L.
Eliminate all uses of the String class. This means you won't be able to use String.toFloat() and will have to write your own function to convert ascii to a float value. Using the String class can cause memory leaks.
Perehama:
Eliminate all uses of the String class. This means you won't be able to use String.toFloat() and will have to write your own function to convert ascii to a float value. Using the String class can cause memory leaks.
Is atof() not supported on Arduino?
Can you post the specifications on the exact format the third party device uses when sending the data?
sterretje:
Is atof() not supported on Arduino?
I don't see it here. EDIT: Whoops!! now I see it. So, you can call atof() or strtod() instead.
Serial.toFloat() boils down to: atof(buffer) which boils down to strtod(buffer, (char **)0), the documentation of which says:
"If the correct value would cause overflow, plus or minus INFINITY is returned (according to the sign of the value), and ERANGE is stored in errno. If the correct value would cause underflow, zero is returned and ERANGE is stored in errno."
It looks like anything over nine 9's and under thirty-nine 9's prints as 'ovf'. Below nine 9's the floating point value prints. At 39 9's and longer the value returned is INFINITY and errno gets set to ERANGE. I think that means that the value is converted up to 39 digits but Serial.print() can't handle floats with more than 10 digits before the decimal point.
In Print.cpp Print::printFloat() you will find:
if (isnan(number)) return print("nan");
if (isinf(number)) return print("inf");
if (number > 4294967040.0) return print ("ovf"); // constant determined empirically
if (number <-4294967040.0) return print ("ovf"); // constant determined empirically
Looks like they want to keep the integer part under 34 bits (sign plus 32-bits of magnitude).
#include <errno.h>
String number = "";
void setup()
{
Serial.begin(115200);
Serial.println();
}
void loop()
{
number += '9';
float f = number.toFloat();
if (f == INFINITY)
Serial.print("INFINITY ");
if (errno == ERANGE)
{
Serial.print("OutOfRange: ");
Serial.println(number);
delay(1000);
}
else
{
Serial.println(f, 7);
}
}
Output
9.0000000
99.0000000
999.0000000
9999.0000000
99999.0000000
999999.0000000
9999999.0000000
100000000.0000000
1000000000.0000000
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
ovf
INFINITY OutOfRange: 999999999999999999999999999999999999999
INFINITY OutOfRange: 9999999999999999999999999999999999999999
INFINITY OutOfRange: 99999999999999999999999999999999999999999
INFINITY OutOfRange: 999999999999999999999999999999999999999999
INFINITY OutOfRange: 9999999999999999999999999999999999999999999
Hey John,
Thanks for illuminating this - your example sketch outlines the problem well and I think I can leverage INFINITY and errno to detect this edge case.
Also thanks to everyone else who chimed in. Unfortunately this is a third-party device so I have to filter the noise on my end with string validation and the likes.
Keep makin'
Elliot
ebradbury:
I have to filter the noise on my end with string validation and the likes.
No one is suggesting that you do not validate the data. I was mostly suggesting that String class will leak memory over time, and your product will fail prematurely.
So, this would, on say a Uno, fragment memory
IntDataPtr[2] = String(strPayloadPtr).toInt();
ePtr[6] = String(strPayloadPtr).toFloat();
?
Idahowalker:
So, this would, on say a Uno, fragment memory
IntDataPtr[2] = String(strPayloadPtr).toInt();
ePtr[6] = String(strPayloadPtr).toFloat();
?
I don't think that above will fragment memory; it also depends on context.
There are probably a few risks in using the String class, but I think that the (main) risk of the String class is in concatenating Strings. I don't use the String class but something like below might fragment memory if it's part of a bigger sketch. The example itself will not fragment memory ( though you will eventually run out of memory
)
String data = "";
void setup()
{
Serial.begin(57600);
}
void loop()
{
while(Serial.available() > 0)
{
char c = Serial.read();
data+=c;
}
}
I wrote an explanation and example to demonstrate what happens under the hood when using String concatenation here; for it to work, you will need to modify the String class as described.