In this comment the OP wrote,
I am a newbie so i would like to know how would i parse the negetive numbers/arguments ?
In this answer @200_success showed an implementation using the strtod function.
The following is my implementation of the strtod function:
A valid floating point number for strtod using the "C" locale is formed by an optional sign character (+ or -), followed by a sequence of digits, optionally containing a decimal-point character (.), optionally followed by an exponent part (an e or E character followed by an optional sign and a sequence of digits).
I'm not using a modern compiler so I didn't implement this portion of the spec.:
If the correct value is out of the range of representable values for the type, a positive or negative HUGE_VAL is returned, and errno is set to ERANGE.
If the correct value would cause underflow, the function returns a value whose magnitude is no greater than the smallest normalized positive number and sets errno to ERANGE.
- Is my implementation correct (does it return correct output for all input)?
- Is it easy to read even without comments?
- Is the set of test cases sufficiently complete?
- Any other suggestions for improvement?1
1(except comments on where I put { }
braces, and whether I use them around single-line control statements
double strtod(const char* str, char** endptr)
{
double result = 0.0;
char signedResult = '\0';
char signedExponent = '\0';
int decimals = 0;
bool isExponent = false;
bool hasExponent = false;
bool hasResult = false;
// exponent is logically int but is coded as double so that its eventual
// overflow detection can be the same as for double result
double exponent = 0;
char c;
for (; '\0' != (c = *str); ++str)
{
if ((c >= '0') && (c <= '9'))
{
int digit = c - '0';
if (isExponent)
{
exponent = (10 * exponent) + digit;
hasExponent = true;
}
else if (decimals == 0)
{
result = (10 * result) + digit;
hasResult = true;
}
else
{
result += (double)digit / decimals;
decimals *= 10;
}
continue;
}
if (c == '.')
{
if (!hasResult)
{
// don't allow leading '.'
break;
}
if (isExponent)
{
// don't allow decimal places in exponent
break;
}
if (decimals != 0)
{
// this is the 2nd time we've found a '.'
break;
}
decimals = 10;
continue;
}
if ((c == '-') || (c == '+'))
{
if (isExponent)
{
if (signedExponent || (exponent != 0))
break;
else
signedExponent = c;
}
else
{
if (signedResult || (result != 0))
break;
else
signedResult = c;
}
continue;
}
if (c == 'E')
{
if (!hasResult)
{
// don't allow leading 'E'
break;
}
if (isExponent)
break;
else
isExponent = true;
continue;
}
// else unexpected character
break;
}
if (isExponent && !hasExponent)
{
while (*str != 'E')
--str;
}
if (!hasResult && signedResult)
--str;
if (endptr)
*endptr = const_cast<char*>(str);
for (; exponent != 0; --exponent)
{
if (signedExponent == '-')
result /= 10;
else
result *= 10;
}
if (signedResult == '-')
{
if (result != 0)
result = -result;
// else I'm not used to working with double-precision numbers so I
// was surprised to find my assert for "-0" failing, saying -0 != +0.
}
return result;
}
// This header is only needed for assert, not for strtod implementation
#include <cstring>
void assert(const char* s, double d, const char* remainder)
{
char* endptr;
double result = strtod(s, &endptr);
if ((result!=d) || strcmp(endptr, remainder))
throw "failed";
}
int main()
{
assert("0", 0, "");
assert("-0", 0, "");
assert("12", 12, "");
assert("23.5", 23.5, "");
assert("-14", -14, "");
assert("-", 0, "-");
assert("-2-a", -2, "-a");
assert("-2a", -2, "a");
assert("0.036", 0.036, "");
assert("12.5E2", 12.5E2, "");
assert("12.5E-3", 12.5E-3, "");
assert("12.5E0", 12.5E0, "");
assert("12.5E", 12.5, "E");
assert("12.5E-", 12.5, "E-");
assert("", 0, "");
assert("a", 0, "a");
assert("E10", 0, "E10");
assert("-E10", 0, "-E10");
assert("-0E10", 0, "");
assert(".3", 0, ".3");
assert("-.3", 0, "-.3");
strtod("42C", 0); // tests endptr == null
assert("+12", 12, "");
assert("+-12", 0, "+-12");
assert("12.5E+3", 12.5E+3, "");
assert("12.5E+-3", 12.5, "E+-3");
}
double
well enough to do that comparison without a helpful header. \$\endgroup\$if (signedResult || (result != 0))
assuming that ifresult
is zero, no input has been read. \$\endgroup\$if (signedResult) result = - result;
The problem was that failed my assert that "-0" ought to return0.0
. Instead it returned-0.0
and I didn't know/understand why0.0 != -0.0
... therefore I added the&& (result != 0)
to the implementation, and didn't negate if result is 0. \$\endgroup\$