Skip to content

Instantly share code, notes, and snippets.

@mattn
Last active October 20, 2020 08:57
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mattn/1890186 to your computer and use it in GitHub Desktop.
Save mattn/1890186 to your computer and use it in GitHub Desktop.
/* vi:set ts=8 sts=4 sw=4: */
/*
* strtod implementation.
* author: Yasuhiro Matsumoto
* license: public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <math.h>
#include <errno.h>
char*
skipwhite(q)
char *q;
{
char *p = q;
while (isspace(*p))
++p;
return p;
}
#define vim_isdigit(x) isdigit(x)
double
vim_strtod(str, end)
const char *str;
char **end;
{
double d = 0.0;
int sign;
const char *p, *a;
a = p = str;
p = skipwhite(p);
/* decimal part */
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
} else if (*p == '+')
++p;
if (vim_isdigit(*p))
{
d = (double)(*p++ - '0');
while (*p && vim_isdigit(*p))
{
d = d * 10.0 + (double)(*p - '0');
++p;
}
a = p;
} else if (*p != '.')
goto done;
d *= sign;
/* fraction part */
if (*p == '.')
{
double f = 0.0;
double base = 0.1;
++p;
if (vim_isdigit(*p))
{
while (*p && vim_isdigit(*p))
{
f += base * (*p - '0') ;
base /= 10.0;
++p;
}
}
d += f * sign;
a = p;
}
/* exponential part */
if ((*p == 'E') || (*p == 'e'))
{
double e = 0.0;
++p;
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
} else if (*p == '+')
++p;
if (vim_isdigit(*p))
{
while (*p == '0')
++p;
e = (double)(*p++ - '0');
while (*p && vim_isdigit(*p))
e = e * 10 + (double)(*p++ - '0');
}
else if (!vim_isdigit(*(a-1)))
{
a = str;
goto done;
}
else if (*p == 0)
goto done;
if (d >= 2.2250738585072011 && e >= 308)
{
d = 0.0;
a = p;
errno = ERANGE;
goto done;
}
d *= pow(10.0, e * sign);
a = p;
}
else if (p > str && !vim_isdigit(*(p-1)))
{
a = str;
goto done;
}
done:
if (end)
*end = (char*)a;
return d;
}
void
test(char* str)
{
double d1, d2, d3;
char *e1, *e2, *e3;
printf("CASE: %s\n", str);
if (
#ifdef _WIN32
setlocale(LC_ALL, "Polish")
#else
setlocale(LC_ALL, "pt_BR.UTF-8")
#endif
== NULL) {
printf("ERR: can't set loacle\n");
return;
}
e1 = NULL;
d1 = vim_strtod(str, &e1);
setlocale(LC_ALL, "");
e2 = NULL;
d2 = vim_strtod(str, &e2);
e3 = NULL;
d3 = strtod(str, &e3);
if (d1 != d2 || d1 != d3 || e1 != e2 || e1 != e3) {
printf("ERR: %s, %s\n", str, strerror(errno));
printf(" E1 %f, %g, %s\n", d1, d1, e1 ? e1 : "");
printf(" E2 %f, %g, %s\n", d2, d2, e2 ? e2 : "");
printf(" E3 %f, %g, %s\n", d3, d3, e3 ? e3 : "");
}
}
int
main()
{
test(".1");
test(" .");
test(" 1.2e3");
test(" +1.2e3");
test("1.2e3");
test("+1.2e3");
test("+1.e3");
test("-1.2e3");
test("-1.2e3.5");
test("-1.2e");
test("--1.2e3.5");
test("--1-.2e3.5");
test("-a");
test("a");
test(".1e");
test(".1e3");
test(".1e-3");
test(".1e-");
test(" .e-");
test(" .e");
test(" e");
test(" e0");
test(" ee");
test(" -e");
test(" .9");
test(" ..9");
test("009");
test("0.09e02");
/* http://thread.gmane.org/gmane.editors.vim.devel/19268/ */
test("0.9999999999999999999999999999999999");
/* PHP (slashdot.jp): http://opensource.slashdot.jp/story/11/01/08/0527259/PHP%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E5%87%A6%E7%90%86%E3%81%AB%E7%84%A1%E9%99%90%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E3%83%90%E3%82%B0 */
test("2.2250738585072011e-308");
/* Gauche: http://blog.practical-scheme.net/gauche/20110203-bitten-by-floating-point-numbers-again */
test("2.2250738585072012e-308");
}
@tkmiller3
Copy link

Thanks for this code. Please note that it fails on exponent values of 0; e.g., 1e0.

I believe it can be fixed by inserting the code if (*p == '\0') --p; after line 97.

@mattn
Copy link
Author

mattn commented May 24, 2020

@tkmiller3 Thanks. Sorry I noticed your comment just now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment