In an answer to this question I mentioned best effort. Here I try to explain what I meant. Please keep in mind that the implementation is intentionally incomplete (missing features such as NAN
are trivial to add) and focuses specifically on the numerical stability.
It was surprisingly hard to get it right, but it paid off: the results match stdlib
implementation even for the most frivolous inputs I could come up with.
However, normalize
is the messiest piece of code I have ever written. It computes three results - that alone is enough to raise some brows. And memmove
looks particularly out of place.
All suggestions are welcome.
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
static char * drop_leading_zeroes(char * start)
{
return start + strspn(start, "0");
}
static char * digit_span(char * start)
{
return start + strspn(start, "0123456789");
}
static int normalize(char ** start, char ** end)
{
int shift = 0;
*start = drop_leading_zeroes(*start);
if (isdigit(**start)) {
*end = digit_span(*start);
shift = *end - *start;
if (**end == '.') {
*end = digit_span(*end + 1);
memmove(*start + 1, *start, shift);
*start += 1;
}
} else if (**start == '.') {
*start += 1;
*end = drop_leading_zeroes(*start);
shift = *start - *end;
*start = *end;
*end = digit_span(*end);
} else {
*end = *start;
}
return shift;
}
static double compute_mantissa(char * start, char * end)
{
double result = 0.0;
while (end != start) {
result = (result + (*--end - '0')) / 10;
}
return result;
}
double my_atof(char * s)
{
bool minus = false;
switch(*s) {
case '-': minus = true;
case '+': ++s; break;
}
char * end;
int exponent = normalize(&s, &end);
double mantissa = compute_mantissa(s, end);
if (minus) {
mantissa = -mantissa;
}
if (tolower(*end) == 'e') {
exponent += atoi(end + 1);
}
return mantissa * pow(10, exponent);
}