Convert a float to a string
Based on Sophy Pal's answer, this is a slightly more complete solution that takes into account the number zero, NaN, infinite, negative numbers, and scientific notation. Albeit sprintf still provides a more accurate string representation.
/*
Double to ASCII Conversion without sprintf.
Roughly equivalent to: sprintf(s, "%.14g", n);
*/
#include <math.h>
#include <string.h>
// For printf
#include <stdio.h>
static double PRECISION = 0.00000000000001;
static int MAX_NUMBER_STRING_SIZE = 32;
/**
* Double to ASCII
*/
char * dtoa(char *s, double n) {
// handle special cases
if (isnan(n)) {
strcpy(s, "nan");
} else if (isinf(n)) {
strcpy(s, "inf");
} else if (n == 0.0) {
strcpy(s, "0");
} else {
int digit, m, m1;
char *c = s;
int neg = (n < 0);
if (neg)
n = -n;
// calculate magnitude
m = log10(n);
int useExp = (m >= 14 || (neg && m >= 9) || m <= -9);
if (neg)
*(c++) = '-';
// set up for scientific notation
if (useExp) {
if (m < 0)
m -= 1.0;
n = n / pow(10.0, m);
m1 = m;
m = 0;
}
if (m < 1.0) {
m = 0;
}
// convert the number
while (n > PRECISION || m >= 0) {
double weight = pow(10.0, m);
if (weight > 0 && !isinf(weight)) {
digit = floor(n / weight);
n -= (digit * weight);
*(c++) = '0' + digit;
}
if (m == 0 && n > 0)
*(c++) = '.';
m--;
}
if (useExp) {
// convert the exponent
int i, j;
*(c++) = 'e';
if (m1 > 0) {
*(c++) = '+';
} else {
*(c++) = '-';
m1 = -m1;
}
m = 0;
while (m1 > 0) {
*(c++) = '0' + m1 % 10;
m1 /= 10;
m++;
}
c -= m;
for (i = 0, j = m-1; i<j; i++, j--) {
// swap without temporary
c[i] ^= c[j];
c[j] ^= c[i];
c[i] ^= c[j];
}
c += m;
}
*(c) = '\0';
}
return s;
}
int main(int argc, char** argv) {
int i;
char s[MAX_NUMBER_STRING_SIZE];
double d[] = {
0.0,
42.0,
1234567.89012345,
0.000000000000018,
555555.55555555555555555,
-888888888888888.8888888,
111111111111111111111111.2222222222
};
for (i = 0; i < 7; i++) {
printf("%d: printf: %.14g, dtoa: %s\n", i+1, d[i], dtoa(s, d[i]));
}
}
Outputs:
- printf: 0, dtoa: 0
- printf: 42, dtoa: 42
- printf: 1234567.8901234, dtoa: 1234567.89012344996444
- printf: 1.8e-14, dtoa: 1.79999999999999e-14
- printf: 555555.55555556, dtoa: 555555.55555555550381
- printf: -8.8888888888889e+14, dtoa: -8.88888888888888e+14
- printf: 1.1111111111111e+23, dtoa: 1.11111111111111e+23
When you're dealing with fp numbers, it can get very compex but the algorithm is simplistic and similar to edgar holleis's answer; kudos! Its complex because when you're dealing with floating point numbers, the calculations will be a little off depending on the precision you've chosen. That's why its not good programming practice to compare a float to a zero.
But there is an answer and this is my attempt at implementing it. Here I've used a tolerance value so you don't end up calculating too many decimal places resulting in an infinite loop. I'm sure there might be better solutions out there but this should help give you a good understanding of how to do it.
char fstr[80];
float num = 2.55f;
int m = log10(num);
int digit;
float tolerance = .0001f;
while (num > 0 + precision)
{
float weight = pow(10.0f, m);
digit = floor(num / weight);
num -= (digit*weight);
*(fstr++)= '0' + digit;
if (m == 0)
*(fstr++) = '.';
m--;
}
*(fstr) = '\0';
You can use C++20 std::format
or the {fmt} library, std::format
is based on, to convert a floating-point number into a string, for example:
std::string s = std::format("{}", M_PI);
The advantage of this method compared to sprintf
is that std::format
gives you the shortest decimal representation with a round-trip guarantee.