It's odd because once a float value is set and when casted it reverts for some reason (e.g. 35.00 casted to int yields 34).
Float will always be an approximation of its value (with 6 to 7 digits precision for 32-bit floats). The best representation for 35.00 is most likely slightly less (e.g. 34.999999). When cast to an int, it will then be 34. To get 35, you need to round (add or subtract 0.5) before casting.
You can also use the built in "lround" function that rounds to a long. If an int is needed, you can just cast the return from lround accordingly.
Unless your implying that the base value (0.35) is really 0.34999999 but if that is the case why would the second value work (e.g. -3.315 outputs -3.32) as it should?
I've change one line and it outputs correctly using "lround".
itoa(lround(dec), &ret[strlen(ret)], 10); //put the frac after the decimal
Can you explain why you are using this "&ret[strlen(ret)]" as the second argument to itoa?
The itoa function expects the second argument to be a character array. You are passing it a characterr (specifically, the character that is the NULL in the string).
Unless your implying that the base value (0.35) is really 0.34999999
I do, all float values will be approximations, unless an exact binary representation exists for the particular decimal number.
but if that is the case why would the second value work (e.g. -3.315 outputs -3.32) as it should?
In the former you cast (drop all decimals) whereas in the latter you round to 2 decimals precision (add +/- 0.005) .
The point is that with float's you never know whether your decimal number will be represented as just above, just below or exactly equal to your source number. Your post suggests you should round rather than cast.
This issue is the main reason why applications dealing with monetary amounts (e.g. banking/accounting) typically use BCD (binary coded decimal) and fixed point arithmetic for numeric calculations. With BCD you're guaranteed an exact representation of any decimal number to a defined precision.
This issue is the main reason why applications dealing with monetary amounts (e.g. banking/accounting) typically use BCD (binary coded decimal) and fixed point arithmetic for numeric calculations. With BCD you're guaranteed an exact representation of any decimal number to a defined precision.
Don't get me wrong I wasn't questioning your response, appreciate your help, and explanation. I was puzzled because I haven't seen this occur with other languages such as Java or C# when writing banking, business or weather based applications. And its been years since I coded anything low level or MCU based (time to dig out my books LOL but it's slowly coming back).
Thanks BenF.
PaulS
Character array or pointer. In this case the call is pointing to the last character in the array and it is not NULL. If it wasn't used the previous character appends would be ignored and the output would not be the same.
// Purpose
// The floatToAscii function converts a floating point
// value and outputs a formatted char array/string.
//
// Input parameters
// value: value to be converted to ascii
// buffer: char array/string buffer for transformation (size of 10min)
// polarized: indicates if output is singed (±) or unsigned
//
// Return
// ±nnn.nn (polarized) or nnn.nn (non-polarized)
char* floatToAscii(float value, char *buffer, boolean polarized)
{
byte pos = 0;
char *ret = buffer;
//
// fixed output length:
// 7 (polarized) or 6 (unpolarized) chars neeeded
// compute rounding-up factor
// note: -existing examples don't round
// negative number correctly
// -0.001 prevents .349999 which
// would yield .34 when casted
if ( value > 0 ) {
value += 0.001;
value += 0.5/(float)100;
} else {
value -= 0.001;
value -= 0.5/(float)100;
}
// validate input value range
if ( value < -999.99 || value > 999.99 ) value = 999.999;
// polarized format includes ±
// while unpolarized is unsigned
if ( polarized ) {
//
if ( value > 0 ) {
ret[pos++] = '+';
} else {
ret[pos++] = '-';
}
}
// set value positive
if (value < 0 ) value = -value;
// pad whole number with leading 0s
if ( value < 10 ) {
ret[pos++] = '0';
ret[pos++] = '0';
} else if ( value > 10 && value < 100 ) {
ret[pos++] = '0';
}
// convert formated whole number
itoa((short)value, ret+pos, 10);
// append decimal place
strcat(ret,".");
//put the frac after the decimal
itoa((value - (short)value) * 100, &ret[strlen(ret)], 10);
// validate output length
if ( polarized) {
if ( strlen(ret) < 7 ) {
// missing padding after decimal
ret[6] = '0';
ret[7] = '\0';
} else if ( strlen(ret) > 7 ) {
// shave off 0s
ret[7] = '\0'; // terminate
}
} else {
if ( strlen(ret) < 6 ) {
// missing padding after decimal
ret[5] = '0';
ret[6] = '\0';
} else if ( strlen(ret) > 6 ) {
// shave off 0s
ret[6] = '\0'; // termniate
}
}
return ret;
}
Thanks again to BenF for his assistance.
FYI: yes sprintf could have been used (original code used it) however it consumes 1.5-2k of ROM. So this was a fun exercise to reduce this functions footprint.
Another approach to "emulating" BCD and fixed-point is to use integer (or long) multiplied by 100.
E.g.
int val1 = 3500; // 35.00
int val2 = 1234; // 12.34
int res;
res = val1 + val2;
Serial.print(res / 100, DEC);
Serial.write('.');
Serial.print(res % 100, DEC);
Your function however turned out fine and it serves as a good reminder for me at least that float have its issues.
You mentioned "sprintf" as a possible solution. The float part (%f) however is stripped from this function intentionally to limit code size, so your function may be more than useful.