From 32e1b2e297f47a12f6a71157ae5df247e076bf0b Mon Sep 17 00:00:00 2001 From: Craig Small Date: Thu, 3 Mar 2016 21:24:08 +1100 Subject: [PATCH] A locale-independent strtod There is a need in some utilities to have a way of accepting both types of decimal points "." and ",". The only way seems to be to rebuild strtod(). This new function will accept "123.456" and "123,456" as 123.456 and considers them the same number. It means we lose thousands separator, but this is rarely used. test scripts are added to check the function returns the proper values. There was simpler predecessor that got stuck on negative 0 or -0.123 which these tests flushed out. References: --- .gitignore | 1 + Makefile.am | 8 +++++- include/strutils.h | 1 + lib/.gitignore | 2 ++ lib/strutils.c | 61 +++++++++++++++++++++++++++++++++++++++++++ lib/test_strtod_nol.c | 45 +++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 lib/test_strtod_nol.c diff --git a/.gitignore b/.gitignore index bebea7dd..4dd11a22 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ slabtop snice stamp-h1 sysctl +test-driver tload top/Makefile top/Makefile.in diff --git a/Makefile.am b/Makefile.am index fe1f12f9..a7216c74 100644 --- a/Makefile.am +++ b/Makefile.am @@ -217,12 +217,15 @@ ps_pscommand_SOURCES = \ ps/stacktrace.c \ lib/fileutils.c +TESTS = lib/test_strtod_nol + # lib/test_* binaries noinst_PROGRAMS = \ lib/test_strutils \ lib/test_fileutils \ lib/test_nsutils \ - lib/test_process + lib/test_process \ + $(TESTS) lib_test_strutils_SOURCES = lib/test_strutils.c lib/strutils.c lib_test_strutils_LDADD = @@ -233,6 +236,9 @@ lib_test_nsutils_LDADD = lib_test_process_SOURCES = lib/test_process.c lib_test_process_LDADD = +lib_test_strtod_nol_SOURCES = lib/test_strtod_nol.c lib/strutils.c +lib_test_strtod_nol_LDADD = + if EXAMPLE_FILES sysconf_DATA = sysctl.conf endif diff --git a/include/strutils.h b/include/strutils.h index 9df33c21..85a61920 100644 --- a/include/strutils.h +++ b/include/strutils.h @@ -7,5 +7,6 @@ extern long strtol_or_err(const char *str, const char *errmesg); extern double strtod_or_err(const char *str, const char *errmesg); +double strtod_nol_or_err(char *str, const char *errmesg); #endif diff --git a/lib/.gitignore b/lib/.gitignore index 7043aff7..e54c8ee2 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -1,5 +1,7 @@ .dirstamp +*.trs test_fileutils test_process test_strutils test_nsutils +test_strtod_nol diff --git a/lib/strutils.c b/lib/strutils.c index 0fd3cf71..e5245db0 100644 --- a/lib/strutils.c +++ b/lib/strutils.c @@ -21,6 +21,7 @@ */ #include +#include #include "c.h" #include "strutils.h" @@ -60,3 +61,63 @@ double strtod_or_err(const char *str, const char *errmesg) error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str); return 0; } + +/* + * Covert a string into a double in a non-locale aware way. + * This means the decimal point can be either . or , + * Also means you cannot use the other for thousands separator + * + * Exits on failure like its other _or_err cousins + */ +double strtod_nol_or_err(char *str, const char *errmesg) +{ + double num; + const char *cp, *radix; + double mult; + int negative = 0; + + if (str != NULL && *str != '\0') { + num = 0.0; + cp = str; + /* strip leading spaces */ + while (isspace(*cp)) + cp++; + + /* get sign */ + if (*cp == '-') { + negative = 1; + cp++; + } else if (*cp == '+') + cp++; + + /* find radix */ + radix = cp; + mult=0.1; + while(isdigit(*radix)) { + radix++; + mult *= 10; + } + while(isdigit(*cp)) { + num += (*cp - '0') * mult; + mult /= 10; + cp++; + } + /* got the integers */ + if (*cp == '\0') + return (negative?-num:num); + if (*cp != '.' && *cp != ',') + error(EXIT_FAILURE, EINVAL, "%s: '%s'", errmesg, str); + + cp++; + mult = 0.1; + while(isdigit(*cp)) { + num += (*cp - '0') * mult; + mult /= 10; + cp++; + } + if (*cp == '\0') + return (negative?-num:num); + } + error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str); + return 0; +} diff --git a/lib/test_strtod_nol.c b/lib/test_strtod_nol.c new file mode 100644 index 00000000..0be798c2 --- /dev/null +++ b/lib/test_strtod_nol.c @@ -0,0 +1,45 @@ + +#include +#include +#include "strutils.h" + +struct strtod_tests { + char *string; + double result; +}; + +struct strtod_tests tests[] = { + {"123", 123.0}, + {"-123", -123.0}, + {"12.34", 12.34}, + {"-12.34", -12.34}, + {".34", 0.34}, + {"-.34", -0.34}, + {"12,34", 12.34}, + {"-12,34", -12.34}, + {",34", 0.34}, + {"-,34", -0.34}, + {"0", 0.0}, + {".0", 0.0}, + {"0.0", 0.0}, + {NULL, 0.0} +}; + + + +int main(int argc, char *argv[]) +{ + int i; + double val; + + for(i=0; tests[i].string != NULL; i++) { + if(strtod_nol_or_err(tests[i].string, "Cannot parse number") != + tests[i].result) { + fprintf(stderr, "FAIL: strtod_nol_or_err(\"%s\") != %f\n", + tests[i].string, tests[i].result); + return EXIT_FAILURE; + } + //fprintf(stderr, "PASS: strtod_nol for %s\n", tests[i].string); + } + return EXIT_SUCCESS; +}