Merge two string into a float on an stm32?

Viewed 2348

I receive two string values over the UART to an stm32f3 and would like to concatenate them to a float, but I actually don't see how to do this. Let's do this in the example below.

char *s1="100";
char *s2="09"; //should result in 100.09

float x=???(s1,s2);

How can I achieve this?

5 Answers

I'd convert each string to an integer, then use arithmetic:

int whole = atoi(s1);
int frac = atoi(s2);

float x = whole + frac / powf(10, strlen(s2));

The last part computes 10^strlen(s2) because "09" means 09/100, "5" means 5/10, etc.

If powf() doesn't work on your system (as you said in a comment), you can use this (only good for small non-negative inputs but that's what you have):

float myexp10(unsigned x)
{
    if (x == 0)
        return 1.0;

    float res = 10;
    while (--x)
        res *= 10;

    return res;
}

One approach is to print both strings into a buffer, and then use strtod to read a float back:

char buf[10];
sprintf(buf, "%.4s.%.4s", s1, s2);
float f = strtod(buf, NULL);
printf("%f\n", f);

Note that since 100.09 has no precise representation as a float, the result would be something close to 100.089996.

Demo.

I would concatenate the two strings, with a dot in between, and then use strtof() to extract the float number, like this:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char *s1 = "100";
    char *s2 = "09";
    char s[strlen(s1) + 1 + strlen(s2) + 1];
    strcpy(s, s1);
    strcat(s, ".");
    strcat(s, s2);
    float result = strtof (s, NULL);
    printf("%f\n", result);
    return 0;
}

Output:

100.089996

If that precision is not enough for you, then use strtod(), like this:

double result = strtod (s, NULL);

Another simple but tedious approach is to store them in char array and use atof() for conversion as follows:

#include <stdio.h>
#include <string.h> /* strlen */
#include <stdlib.h> /* atof */


float str2float(const char* s1, const char* s2)
{
    int size = strlen(s1) + strlen(s2) + 2;
    char a[size];

    for(int i = 0; i < strlen(s1); ++i)
        a[i] = s1[i];
    a[strlen(s1)] = '.';

    for(int i = strlen(s1)+1, j = 0; i < size && j < strlen(s2); ++i, ++j)
        a[i] = s2[j];

    a[size] = '\0';

    return (float)atof(a);
}

int main(void)
{
    char *s1="100";
    char *s2="09"; 

    printf("%.2f\n", str2float(s1,s2));
    return 0;
}

Consider scaling the fractional part and adding as suggest by others.

  char *s1="100";
  char *s2="09";
  float f1 = strtof(s1, (void*) NULL); 
  unsigned n = strlen(s2);
  float f2 = strtof(s2, (void*) NULL)/powf(10, n);
  return f1 + f2;

This is usually sufficient, yet to dig a bit deeper.

strtof(s2, (void*) NULL)/powf(10, n); incurs a rounding with the division as the quotient, a fraction, is rarely exactly representable as a binary32. Then the addition f1 + f2 may incur another rounding. The sum is sometimes then not the best float answer due to double rounding.

To greatly reduce the chance of double rounding, code can easily use higher precision like double.

  float f1 = strtof(s1, (void*) NULL); 
  unsigned n = strlen(s2);
  double f2 = strtod(s2, (void*) NULL)/pow(10, n); // use higher precision
  return f1 + f2;

Alternatively code may avoid wider FP math and use exact integer math first to combine the values and then divide, incurring only 1 rounding. This will make for a more accurate answer once in a while.

  long l1 = strtol(s1, (void*) NULL, 10); 
  long l2 = strtol(s2, (void*) NULL, 10); 
  unsigned n = strlen(s2);
  long p10 = pow10(n);  // Trivial user TBD code

  long sum = l1*p10 + l2;  // Exact unless `long` overflow
  return 1.0f*sum/p10;     // `float` conversion exact for value up to about 1/FLT_EPSILON

The above works well if s1 is negative. Also OK with s2 as a negative aside from more code needed to extract the digit width than strlen(s2).

Else it is hard to beat a string concatenation and convert. I recommend float strof() over double atof() as 1) atof() lacks defined behavior on overflow, 2) atof() uses double math, which may be slower.

size_t l1 = strlen(s1);
size_t l2 = strlen(s1);
char buffer[l1 + 1 + l2 + 1];  // Or use a fixed wide buffer with test to insure fit
strcpy(buffer, s1);
buffer[l1] = '.';
strcpy(buffer + l1 + 1, s1);
return strtof(buffer, (void *) NULL);
Related