Faced with converting floating-point values from a sensor obtained to string on an embedded system to transmit over UART, I came up with the following dbl2str()
to handle either float
or double
input. The accuracy of the last digit in the fractional part wasn’t important as the floating point-values were from a temperature sensor on an MSP432. The intent was to avoid loading stdio.h
and math.h
.
The double value, an adequately sized buffer and then precision for the fractional-part are parameters to the function:
/**
* convert double d to string with fractional part
* limited to prec digits. s must have adequate
* storage to hold the converted value.
*/
char *dbl2str (double d, char *s, int prec);
The approach is:
- Handle
0.0
case where integer-part is'0'
and pad fractional part toprec
'0'
s, return at that point. - Save
sign
flag (1
-negative,0
-posititve), set padding variablezeros
equal toprec
, change sign of floating-point value to positive if negative. - Nul-terminate temp string and fill from end with fractional-part conversion, subtracting
1
fromzeros
on each iteration, and after leaving conversion loop, pad to remainingzeros
. - Add separator
'.'
and continue to fill temp string with integer-part conversion. - if
sign
add'-'
to front of temp string. - copy temp string to buffer and return pointer to buffer.
(note: the range of floating-point values is from roughly -50.0
to 200.00
so INF
was not protected against, nor was exhausting of the 32-byte buffer a consideration)
The code with test case is:
#include <stdio.h>
#include <stdint.h>
#define FPMAXC 32
/**
* convert double d to string with fractional part
* limited to prec digits. s must have adequate
* storage to hold the converted value.
*/
char *dbl2str (double d, char *s, int prec)
{
if (d == 0) { /* handle zero case */
int i = 0;
*s = '0'; /* single '0' for int part */
s(1) = '.'; /* separator */
for (i = 2; i < 2 + prec; i++) /* pad fp to prec with '0' */
s(i) = '0';
s(i) = 0; /* nul-terminate */
return s;
}
char tmp(FPMAXC), *p = tmp + FPMAXC - 1; /* tmp buf, ptr to end */
int sign = d < 0 ? 1 : 0, /* set sign if negative */
mult = 1; /* multiplier for precision */
unsigned zeros = prec; /* padding zeros for fp */
uint64_t ip, fp; /* integer & fractional parts */
if (sign) /* work with positive value */
d = -d;
for (int i = 0; i < prec; i++) /* compute multiplier */
mult *= 10;
ip = (uint64_t)d; /* set integer part */
fp = (uint64_t)((d - ip) * mult); /* fractional part to prec */
*p = 0; /* nul-terminate tmp */
while (fp) { /* convert fractional part */
*--p = fp % 10 + '0';
fp /= 10;
if (zeros) /* decrement zero pad */
zeros--;
}
while (zeros--) /* pad reaming zeros */
*--p = '0';
*--p = '.';
if (!ip) /* no integer part */
*--p = '0';
else
while (ip) { /* convert integer part */
*--p = ip % 10 + '0';
ip /= 10;
}
if (sign) /* if sign, add '-' */
*--p = '-';
for (int i = 0;; i++, p++) { /* copy to s with */
s(i) = *p;
if (!*p)
break;
}
return s;
}
int main (void) {
char buf(FPMAXC);
double d = 123.45678;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = -d;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = 0.;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = 0.12345;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = -d;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = 123.0;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
d = -d;
printf ("% 8.3lf => %8sn", d, dbl2str (d, buf, 3));
}
The function does what I intended, but would like to know if there are any obvious improvements that can be made with a slight-eye on optimization.
Program Output
./bin/dbl2str
123.457 => 123.456
-123.457 => -123.456
0.000 => 0.000
0.123 => 0.123
-0.123 => -0.123
123.000 => 123.000
-123.000 => -123.000