Casting Float to Int

Hello all,

Been playing around with formating float to a string for my specific needs but encountered a problem.

Here is the complete sketch I am testing with:

#include <Streaming.h>         // Include the Streaming library
#include <MemoryFree.h>        // Include the MemoryFree library

char buffer[32];

char* floatToAscii(float value, char *buffer, boolean polarized)
{
  float dec;
  char *ret = buffer;
  
  // compute rounding-up factor
  if ( value > 0 )
    value += 0.5/(float)100;
  else
    value -= 0.5/(float)100;
    
  Serial.print("Rounded: ");
  Serial.println(value);
  
  // polarized format includes ±
  // while unpolarized is unsigned
  if ( polarized ) {
    //
    if ( value > 0 ) {
      ret[0] = '+';
      itoa((int)value,ret+1,10);
    } else {
      itoa((int)value,ret,10);
    }
  } else {
    // check for signed value
    // e.g. non-polarized always +
    if (value < 0 ) value = -value;
    //
    itoa((int)value,ret,10);
  }
  
  dec = (value - (int)value) * 100;

  // check for signed value
  if ( dec < 0 ) dec = -dec;
  
  float f = 0.35;
  
  Serial.print("Decimal (int)0.35: ");
  Serial.print( (int)(f * 100) );
  Serial.print(" Decimal (float): ");
  Serial.print( dec );
  Serial.print(" Decimal (int): ");
  Serial.println( (int)dec );

  strcat(ret,".");
  itoa(dec,&ret[strlen(ret)],10); //put the frac after the deciaml

  return ret;
}

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  //
  //Serial << floatToAscii(99.1236, buffer, false) << endl;
  //Serial << floatToAscii(-23.3219, buffer, false) << endl;
  Serial << floatToAscii(89.345, buffer, true) << endl;
  Serial << floatToAscii(-3.315, buffer, true) << endl;
  Serial.println();
  
  delay(10000);
}

Outputs:

Rounded: 89.35

Decimal (int)0.35: 35 Decimal (float): 35.00 Decimal (int): 34

+89.34

Rounded: -3.32

Decimal (int)0.35: 35 Decimal (float): 32.00 Decimal (int): 32

-3.32

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).

Has anyone else encountered this or is there something wrong with how I am casting.

Any help would be greatly appreciated.

Cheers,

Eric

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.

Yes, however, it is being rounded already.

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.

Completed function

// 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.