Convert float to string without sprintf()

31,564

Solution 1

Try this. It should be nice and small. I've output the string directly - doing a printf, rather than a sprintf. I'll leave it to you to allocate space for the return string, as well as copying the result into it.

// prints a number with 2 digits following the decimal place
// creates the string backwards, before printing it character-by-character from
// the end to the start
//
// Usage: myPrintf(270.458)
//  Output: 270.45
void myPrintf(float fVal)
{
    char result[100];
    int dVal, dec, i;

    fVal += 0.005;   // added after a comment from Matt McNabb, see below.

    dVal = fVal;
    dec = (int)(fVal * 100) % 100;

    memset(result, 0, 100);
    result[0] = (dec % 10) + '0';
    result[1] = (dec / 10) + '0';
    result[2] = '.';

    i = 3;
    while (dVal > 0)
    {
        result[i] = (dVal % 10) + '0';
        dVal /= 10;
        i++;
    }

    for (i=strlen(result)-1; i>=0; i--)
        putc(result[i], stdout);
}

Solution 2

Here's a version optimized for embedded systems that doesn't require any stdio or memset, and has low memory footprint. You're responsible for passing a char buffer initialized with zeros (with pointer p) where you want to store your string, and defining CHAR_BUFF_SIZE when you make said buffer (so the returned string will be null terminated).

static char * _float_to_char(float x, char *p) {
    char *s = p + CHAR_BUFF_SIZE; // go to end of buffer
    uint16_t decimals;  // variable to store the decimals
    int units;  // variable to store the units (part to left of decimal place)
    if (x < 0) { // take care of negative numbers
        decimals = (int)(x * -100) % 100; // make 1000 for 3 decimals etc.
        units = (int)(-1 * x);
    } else { // positive numbers
        decimals = (int)(x * 100) % 100;
        units = (int)x;
    }

    *--s = (decimals % 10) + '0';
    decimals /= 10; // repeat for as many decimal places as you need
    *--s = (decimals % 10) + '0';
    *--s = '.';

    while (units > 0) {
        *--s = (units % 10) + '0';
        units /= 10;
    }
    if (x < 0) *--s = '-'; // unary minus sign for negative numbers
    return s;
}

Tested on ARM Cortex M0 & M4. Rounds correctly.

Solution 3

// convert float to string one decimal digit at a time
// assumes float is < 65536 and ARRAYSIZE is big enough
// problem: it truncates numbers at size without rounding
// str is a char array to hold the result, float is the number to convert
// size is the number of decimal digits you want


void FloatToStringNew(char *str, float f, char size)

{

char pos;  // position in string

    char len;  // length of decimal part of result

    char* curr;  // temp holder for next digit

    int value;  // decimal digit(s) to convert

    pos = 0;  // initialize pos, just to be sure

    value = (int)f;  // truncate the floating point number
    itoa(value,str);  // this is kinda dangerous depending on the length of str
    // now str array has the digits before the decimal

    if (f < 0 )  // handle negative numbers
    {
        f *= -1;
        value *= -1;
    }

     len = strlen(str);  // find out how big the integer part was
    pos = len;  // position the pointer to the end of the integer part
    str[pos++] = '.';  // add decimal point to string

    while(pos < (size + len + 1) )  // process remaining digits
    {
        f = f - (float)value;  // hack off the whole part of the number
        f *= 10;  // move next digit over
        value = (int)f;  // get next digit
        itoa(value, curr); // convert digit to string
        str[pos++] = *curr; // add digit to result string and increment pointer
    }
 }

Solution 4

I can't comment on enhzflep's response, but to handle negative numbers correctly (which the current version does not), you only need to add

if (fVal < 0) {
     putc('-', stdout);
     fVal = -fVal;
  }

at the beginning of the function.

Solution 5

While you guys were answering I've come up with my own solution which that works better for my application and I figure I'd share. It doesn't convert the float to a string, but rather 8-bit integers. My range of numbers is very small (0-15) and always non-negative, so this will allow me to send the data over bluetooth to my android app.

//Assumes bytes* is at least 2-bytes long
void floatToBytes(byte_t* bytes, float flt)
{
  bytes[1] = (byte_t) flt;    //truncate whole numbers
  flt = (flt - bytes[1])*100; //remove whole part of flt and shift 2 places over
  bytes[0] = (byte_t) flt;    //truncate the fractional part from the new "whole" part
}
//Example: 144.2345 -> bytes[1] = 144; -> bytes[0] = 23
Share:
31,564
audiFanatic
Author by

audiFanatic

Updated on January 05, 2022

Comments

  • audiFanatic
    audiFanatic over 2 years

    I'm coding for a microcontroller-based application and I need to convert a float to a character string, but I do not need the heavy overhead associated with sprintf(). Is there any eloquent way to do this? I don't need too much. I only need 2 digits of precision.

  • M.M
    M.M about 10 years
    Perhaps add 0.005 to fVal at the start; that way you don't end up with things like 269.9999834 being printed as 269.99 instead of 270.00.
  • chux - Reinstate Monica
    chux - Reinstate Monica about 10 years
    Note: This loses part of the "2 digits precision" as directed in the post. Simple fix: flt += 0.005 first.
  • Beta Decay
    Beta Decay over 9 years
    @chux What would I do if I wanted 9 digit precision?
  • chux - Reinstate Monica
    chux - Reinstate Monica over 9 years
    @user12321 The quick answer is flt += 1e-9/2; ... flt = (flt - bytes[1])*1e9; but other issues like integer math overflow may come into play elsewhere. Better to craft an answer after you provide detail of your desired range and type of expected input/output.
  • Murat Şeker
    Murat Şeker almost 7 years
    absbuffer is not deallocated
  • chux - Reinstate Monica
    chux - Reinstate Monica about 6 years
    (int)(fVal * 100) readily fails int conversion due to being out of range. Yet it appears OP is not concerned about that.
  • Antti Haapala -- Слава Україні
    Antti Haapala -- Слава Україні almost 5 years
    indeed, you should compare the value against INT_MAX/100 and INT_MIN/100 before... unless the range is guaranteed to be within
  • Joshua Webb
    Joshua Webb over 4 years
    Note: this will convert 0 to .00, if you want 0.00, use a do..while instead
  • Ingmar
    Ingmar over 3 years
    Two digits after the dot - OK. I needed a quick solution since printf() does not work with my CubeIDE project, although I set -u _printf_float and the linker script seems to be OK. Your functions suits my needs as I only needed a rough float output on the debug console to see whether the value is there. Awesome and thank you.