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:
This commit is contained in:
Craig Small 2016-03-03 21:24:08 +11:00
parent 1ec2b0eeb1
commit 32e1b2e297
6 changed files with 117 additions and 1 deletions

1
.gitignore vendored
View File

@ -50,6 +50,7 @@ slabtop
snice
stamp-h1
sysctl
test-driver
tload
top/Makefile
top/Makefile.in

View File

@ -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

View File

@ -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

2
lib/.gitignore vendored
View File

@ -1,5 +1,7 @@
.dirstamp
*.trs
test_fileutils
test_process
test_strutils
test_nsutils
test_strtod_nol

View File

@ -21,6 +21,7 @@
*/
#include <stdlib.h>
#include <ctype.h>
#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;
}

45
lib/test_strtod_nol.c Normal file
View File

@ -0,0 +1,45 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}