diff --git a/src/libCom/error/Makefile b/src/libCom/error/Makefile index 5b3c2c3c3..ce9ca659a 100644 --- a/src/libCom/error/Makefile +++ b/src/libCom/error/Makefile @@ -23,6 +23,7 @@ Com_SRCS += errSymTbl.c # ERR_S_FILES += $(LIBCOM)/osi/devLib.h ERR_S_FILES += $(LIBCOM)/as/asLib.h +ERR_S_FILES += $(LIBCOM)/misc/epicsStdlib.h ERR_S_FILES += $(SRC)/ioc/db/dbAccessDefs.h ERR_S_FILES += $(SRC)/ioc/dbStatic/devSup.h ERR_S_FILES += $(SRC)/ioc/dbStatic/drvSup.h diff --git a/src/libCom/error/errMdef.h b/src/libCom/error/errMdef.h index a30650a44..80483bca1 100644 --- a/src/libCom/error/errMdef.h +++ b/src/libCom/error/errMdef.h @@ -48,6 +48,7 @@ extern "C" { #define M_casApp (524 <<16) /*CA server application*/ #define M_bucket (525 <<16) /*Bucket Hash*/ #define M_gddFuncTbl (526 <<16) /*gdd jump table*/ +#define M_stdlib (527 <<16) /*EPICS Standard library*/ epicsShareFunc void epicsShareAPI errSymLookup(long status, char *pBuf, unsigned bufLength); epicsShareFunc void epicsShareAPI errSymTest(unsigned short modnum, unsigned short begErrNum, unsigned short endErrNum); diff --git a/src/libCom/misc/epicsStdlib.c b/src/libCom/misc/epicsStdlib.c index 494fc60be..6538948a2 100644 --- a/src/libCom/misc/epicsStdlib.c +++ b/src/libCom/misc/epicsStdlib.c @@ -1,50 +1,248 @@ /*************************************************************************\ -* Copyright (c) 2009 UChicago Argonna LLC, as Operator of Argonne +* Copyright (c) 2012 UChicago Argonna LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ -/*epicsStdlib.c*/ -/*Author: Eric Norum */ +/* $Revision-Id$ */ +/* Authors: Eric Norum & Andrew Johnson */ #include #include +#include +#include #define epicsExportSharedSymbols #include "epicsMath.h" #include "epicsStdlib.h" #include "epicsString.h" +#include "epicsConvert.h" -epicsShareFunc int epicsScanDouble(const char *str, double *dest) +/* These are the conversion primitives */ + +epicsShareFunc int +epicsParseLong(const char *str, long *to, int base) { + int c; char *endp; - double dtmp; + long value; + + while ((c = *str) && isspace(c)) + ++str; + + errno = 0; + value = strtol(str, &endp, base); - dtmp = epicsStrtod(str, &endp); if (endp == str) - return 0; - *dest = dtmp; - return 1; + return S_stdlib_noConversion; + if (errno == EINVAL) /* Not universally supported */ + return S_stdlib_badBase; + + while ((c = *endp) && isspace(c)) + ++endp; + if (c) + return S_stdlib_extraneous; + + if (errno == ERANGE) + return S_stdlib_overflow; + + *to = value; + return 0; } -epicsShareFunc int epicsScanFloat(const char *str, float *dest) +epicsShareFunc int +epicsParseULong(const char *str, unsigned long *to, int base) { + int c; char *endp; - double dtmp; + unsigned long value; + + while ((c = *str) && isspace(c)) + ++str; + + errno = 0; + value = strtoul(str, &endp, base); - dtmp = epicsStrtod(str, &endp); if (endp == str) - return 0; - *dest = (float)dtmp; - return 1; + return S_stdlib_noConversion; + if (errno == EINVAL) /* Not universally supported */ + return S_stdlib_badBase; + + while ((c = *endp) && isspace(c)) + ++endp; + if (c) + return S_stdlib_extraneous; + + if (errno == ERANGE) + return S_stdlib_overflow; + + *to = value; + return 0; } -/* Systems with a working strtod() just #define epicsStrtod strtod */ +epicsShareFunc int +epicsParseDouble(const char *str, double *to) +{ + int c; + char *endp; + double value; + + while ((c = *str) && isspace(c)) + ++str; + + errno = 0; + value = epicsStrtod(str, &endp); + if (endp == str) + return S_stdlib_noConversion; + + while ((c = *endp) && isspace(c)) + ++endp; + if (c) + return S_stdlib_extraneous; + + if (errno == ERANGE) + return (value == 0) ? S_stdlib_underflow : S_stdlib_overflow; + + *to = value; + return 0; +} + + +/* These call the primitives */ + +epicsShareFunc int +epicsParseInt8(const char *str, epicsInt8 *to, int base) +{ + long value; + int status = epicsParseLong(str, &value, base); + + if (status) + return status; + + if (value < -0x80 || value > 0x7f) + return S_stdlib_overflow; + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseUInt8(const char *str, epicsUInt8 *to, int base) +{ + unsigned long value; + int status = epicsParseULong(str, &value, base); + + if (status) + return status; + + if (value > 0xff) + return S_stdlib_overflow; + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseInt16(const char *str, epicsInt16 *to, int base) +{ + long value; + int status = epicsParseLong(str, &value, base); + + if (status) + return status; + + if (value < -0x8000 || value > 0x7fff) + return S_stdlib_overflow; + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseUInt16(const char *str, epicsUInt16 *to, int base) +{ + unsigned long value; + int status = epicsParseULong(str, &value, base); + + if (status) + return status; + + if (value > 0xffff) + return S_stdlib_overflow; + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseInt32(const char *str, epicsInt32 *to, int base) +{ + long value; + int status = epicsParseLong(str, &value, base); + + if (status) + return status; + +#if (LONG_MAX > 0x7fffffff) + if (value < -0x80000000L || value > 0x7fffffffL) + return S_stdlib_overflow; +#endif + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseUInt32(const char *str, epicsUInt32 *to, int base) +{ + unsigned long value; + int status = epicsParseULong(str, &value, base); + + if (status) + return status; + +#if (ULONG_MAX > 0xffffffff) + if (value > 0xffffffffL) + return S_stdlib_overflow; +#endif + + *to = value; + return 0; +} + +epicsShareFunc int +epicsParseFloat(const char *str, float *to) +{ + double value, abs; + int status = epicsParseDouble(str, &value); + + if (status) + return status; + + abs = fabs(value); + if (value > 0 && abs <= FLT_MIN) + return S_stdlib_underflow; + if (finite(value) && abs >= FLT_MAX) + return S_stdlib_overflow; + + *to = value; + return 0; +} + + +/* If strtod() works properly, osdStrtod.h defines this macro: + * #define epicsStrtod strtod + * + * If strtod() is broken, osdStrtod.h defines this prototype: + * epicsShareFunc double epicsStrtod(const char *str, char **endp); + */ + #ifndef epicsStrtod -epicsShareFunc double epicsStrtod(const char *str, char **endp) +epicsShareFunc double +epicsStrtod(const char *str, char **endp) { const char *cp = str; int negative = 0; @@ -60,6 +258,12 @@ epicsShareFunc double epicsStrtod(const char *str, char **endp) cp++; } + if (epicsStrnCaseCmp("0x", cp, 2) == 0) { + if (negative) + return strtol(str, endp, 16); + else + return strtoul(str, endp, 16); + } if (!isalpha((int)*cp)) return strtod(str, endp); diff --git a/src/libCom/misc/epicsStdlib.h b/src/libCom/misc/epicsStdlib.h index 84d4af062..81d31983f 100644 --- a/src/libCom/misc/epicsStdlib.h +++ b/src/libCom/misc/epicsStdlib.h @@ -1,26 +1,67 @@ /*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found +* EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ -/*epicsStdlib.h*/ -/*Author: Eric Norum */ +/* epicsStdlib.h */ +/* Author: Eric Norum */ -#include +#include +#include + +#include "shareLib.h" +#include "osdStrtod.h" +#include "epicsTypes.h" +#include "errMdef.h" #ifdef __cplusplus extern "C" { #endif -epicsShareFunc int epicsScanDouble(const char *str, double *dest); -epicsShareFunc int epicsScanFloat(const char *str, float *dest); +#define S_stdlib_noConversion (M_stdlib | 1) /* No digits to convert */ +#define S_stdlib_extraneous (M_stdlib | 2) /* Extraneous characters */ +#define S_stdlib_underflow (M_stdlib | 3) /* Too small to represent */ +#define S_stdlib_overflow (M_stdlib | 4) /* Too large to represent */ +#define S_stdlib_badBase (M_stdlib | 5) /* Number base not supported */ -#include -#include + +epicsShareFunc int + epicsParseLong(const char *str, long *to, int base); +epicsShareFunc int + epicsParseULong(const char *str, unsigned long *to, int base); +epicsShareFunc int + epicsParseDouble(const char *str, double *to); + +epicsShareFunc int + epicsParseFloat(const char *str, float *to); + +epicsShareFunc int + epicsParseInt8(const char *str, epicsInt8 *to, int base); +epicsShareFunc int + epicsParseUInt8(const char *str, epicsUInt8 *to, int base); +epicsShareFunc int + epicsParseInt16(const char *str, epicsInt16 *to, int base); +epicsShareFunc int + epicsParseUInt16(const char *str, epicsUInt16 *to, int base); + +epicsShareFunc int + epicsParseInt32(const char *str, epicsInt32 *to, int base); +epicsShareFunc int + epicsParseUInt32(const char *str, epicsUInt32 *to, int base); + +#define epicsParseFloat32(str, to) epicsParseFloat(str, to) +#define epicsParseFloat64(str, to) epicsParseDouble(str, to) + +/* These macros return 1 if successful, 0 on failure. + * This is analagous to the return value from sscanf() + */ +#define epicsScanLong(str, to, base) !epicsParseLong(str, to, base) +#define epicsScanULong(str, to, base) !epicsParseULong(str, to, base) +#define epicsScanFloat(str, to) !epicsParseFloat(str, to) +#define epicsScanDouble(str, to) !epicsParseDouble(str, to) #ifdef __cplusplus } diff --git a/src/libCom/test/Makefile b/src/libCom/test/Makefile index fc77cf5c4..f38b01dbf 100644 --- a/src/libCom/test/Makefile +++ b/src/libCom/test/Makefile @@ -54,6 +54,11 @@ epicsStdioTest_SRCS += epicsStdioTest.c testHarness_SRCS += epicsStdioTest.c TESTS += epicsStdioTest +TESTPROD_HOST += epicsStdlibTest +epicsStdlibTest_SRCS += epicsStdlibTest.c +testHarness_SRCS += epicsStdlibTest.c +TESTS += epicsStdlibTest + TESTPROD_HOST += epicsStringTest epicsStringTest_SRCS += epicsStringTest.c testHarness_SRCS += epicsStringTest.c diff --git a/src/libCom/test/epicsRunLibComTests.c b/src/libCom/test/epicsRunLibComTests.c index 145f84076..8fa3f837f 100644 --- a/src/libCom/test/epicsRunLibComTests.c +++ b/src/libCom/test/epicsRunLibComTests.c @@ -28,6 +28,7 @@ int epicsMathTest(void); int epicsMessageQueueTest(void); int epicsMutexTest(void); int epicsStdioTest(void); +int epicsStdlibTest(void); int epicsStringTest(void); int epicsThreadOnceTest(void); int epicsThreadPriorityTest(void); @@ -76,6 +77,8 @@ void epicsRunLibComTests(void) runTest(epicsStdioTest); + runTest(epicsStdlibTest); + runTest(epicsStringTest); runTest(epicsThreadOnceTest); diff --git a/src/libCom/test/epicsStdlibTest.c b/src/libCom/test/epicsStdlibTest.c new file mode 100644 index 000000000..362df3aac --- /dev/null +++ b/src/libCom/test/epicsStdlibTest.c @@ -0,0 +1,440 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* epicsStdlibTest.c + * + * Author Andrew Johnson + */ + +#include +#include +#include +#include + +#include "epicsStdlib.h" +#include "epicsMath.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* Implement the epicsParseDouble() API for checking the native stdtod() + * so we can tell the user if our epicsStrtod() wrapper is unnecessary. + */ +int +parseStrtod(const char *str, double *to) +{ + int c; + char *endp; + double dtmp; + + while ((c = *str) && isspace(c)) + ++str; + + errno = 0; + dtmp = strtod(str, &endp); + if (endp == str) + return S_stdlib_noConversion; + + while ((c = *endp) && isspace(c)) + ++endp; + if (c) + return S_stdlib_extraneous; + + if (dtmp == 0 && errno == ERANGE) + return S_stdlib_underflow; + if (fabs(dtmp) == HUGE_VAL && errno == ERANGE) + return S_stdlib_overflow; + + *to = dtmp; + return 0; +} +#define scanStrtod(str, to) !parseStrtod(str, to) + + +MAIN(epicsStdlibTest) +{ + unsigned long u; + long l; + double d; + float f; + epicsInt8 i8; + epicsUInt8 u8; + epicsInt16 i16; + epicsUInt16 u16; + epicsInt32 i32; + epicsUInt32 u32; + + testPlan(135); + + testOk(epicsParseLong("", &l, 0) == S_stdlib_noConversion, + "Long '' => noConversion"); + testOk(epicsParseULong("", &u, 0) == S_stdlib_noConversion, + "ULong '' => noConversion"); + testOk(epicsParseFloat("", &f) == S_stdlib_noConversion, + "Float '' => noConversion"); + testOk(epicsParseDouble("", &d) == S_stdlib_noConversion, + "Double '' => noConversion"); + + testOk(epicsParseLong("\t \n", &l, 0) == S_stdlib_noConversion, + "Long '\\t 1\\n' => noConversion"); + testOk(epicsParseULong("\t \n", &u, 0) == S_stdlib_noConversion, + "ULong '\\t 1\\n' => noConversion"); + testOk(epicsParseFloat("\t \n", &f) == S_stdlib_noConversion, + "Float '\\t 1\\n' => noConversion"); + testOk(epicsParseDouble("\t \n", &d) == S_stdlib_noConversion, + "Double '\\t 1\\n' => noConversion"); + + testOk(epicsParseLong("!", &l, 0) == S_stdlib_noConversion, + "Long '!' => noConversion"); + testOk(epicsParseULong("!", &u, 0) == S_stdlib_noConversion, + "ULong '!' => noConversion"); + testOk(epicsParseFloat("!", &f) == S_stdlib_noConversion, + "Float '!' => noConversion"); + testOk(epicsParseDouble("!", &d) == S_stdlib_noConversion, + "Double '!' => noConversion"); + + testOk(epicsScanLong("0", &l, 0) && l == 0, "Long '0'"); + testOk(epicsScanULong("0", &u, 0) && u == 0, "ULong '0'"); + testOk(epicsScanFloat("0", &f) && f == 0, "Float '0'"); + testOk(epicsScanDouble("0", &d) && d == 0, "Double '0'"); + + testOk(epicsScanLong("\t 1\n", &l, 0) && l == 1, "Long '\\t 1\\n'"); + testOk(epicsScanULong("\t 1\n", &u, 0) && u == 1, "ULong '\\t 1\\n'"); + testOk(epicsScanFloat("\t 1\n", &f) && f == 1, "Float '\\t 1\\n'"); + testOk(epicsScanDouble("\t 1\n", &d) && d == 1, "Double '\\t 1\\n'"); + + testOk(epicsParseLong("2!", &l, 0) == S_stdlib_extraneous, + "Long '2!' => extraneous"); + testOk(epicsParseULong("2!", &u, 0) == S_stdlib_extraneous, + "ULong '2!' => extraneous"); + testOk(epicsParseFloat("2!", &f) == S_stdlib_extraneous, + "Float '2!' => extraneous"); + testOk(epicsParseDouble("2!", &d) == S_stdlib_extraneous, + "Double '2!' => extraneous"); + + testOk(epicsParseLong("3 !", &l, 0) == S_stdlib_extraneous, + "Long '3 !' => extraneous"); + testOk(epicsParseULong("3 !", &u, 0) == S_stdlib_extraneous, + "ULong '3 !' => extraneous"); + testOk(epicsParseFloat("3 !", &f) == S_stdlib_extraneous, + "Float '3 !' => extraneous"); + testOk(epicsParseDouble("3 !", &d) == S_stdlib_extraneous, + "Double '3 !' => extraneous"); + + testOk(epicsScanLong("0x0", &l, 0) && l == 0, "Long '0x0'"); + testOk(epicsScanULong("0x0", &u, 0) && u == 0, "ULong '0x0'"); + testOk(epicsScanFloat("0x0", &f) && f == 0, "Float '0x0'"); + testOk(epicsScanDouble("0x0", &d) && d == 0, "Double '0x0'"); + + testOk(epicsScanLong("0x1", &l, 0) && l == 1, "Long '0x1'"); + testOk(epicsScanULong("0x1", &u, 0) && u == 1, "ULong '0x1'"); + testOk(epicsScanFloat("0x1", &f) && f == 1, "Float '0x1'"); + testOk(epicsScanDouble("0x1", &d) && d == 1, "Double '0x1'"); + + testOk(epicsScanLong("+0x1", &l, 0) && l == 1, "Long '+0x1'"); + testOk(epicsScanULong("+0x1", &u, 0) && u == 1, "ULong '+0x1'"); + testOk(epicsScanFloat("+0x1", &f) && f == 1, "Float '+0x1'"); + testOk(epicsScanDouble("+0x1", &d) && d == 1, "Double '+0x1'"); + + testOk(epicsScanLong("-0x1", &l, 0) && l == -1, "Long '-0x1'"); + testOk(epicsScanULong("-0x1", &u, 0) && u == -1, "ULong '-0x1'"); + testOk(epicsScanFloat("-0x1", &f) && f == -1, "Float '-0x1'"); + testOk(epicsScanDouble("-0x1", &d) && d == -1, "Double '-0x1'"); + + testOk(epicsScanLong("0xf", &l, 0) && l == 15, "Long '0xf'"); + testOk(epicsScanULong("0xf", &u, 0) && u == 15, "ULong '0xf'"); + testOk(epicsScanFloat("0xf", &f) && f == 15, "Float '0xf'"); + testOk(epicsScanDouble("0xf", &d) && d == 15, "Double '0xf'"); + + testOk(epicsScanLong("0XF", &l, 0) && l == 15, "Long '0XF'"); + testOk(epicsScanULong("0XF", &u, 0) && u == 15, "ULong '0XF'"); + testOk(epicsScanFloat("0XF", &f) && f == 15, "Float '0XF'"); + testOk(epicsScanDouble("0XF", &d) && d == 15, "Double '0XF'"); + + testOk(epicsParseLong("0x0", &l, 10) == S_stdlib_extraneous, + "Long '0x0' in base 10 => extraneous"); + testOk(epicsParseULong("0x0", &u, 10) == S_stdlib_extraneous, + "ULong '0x0' in base 10 => extraneous"); + testOk(epicsScanLong("0x10", &l, 0) && l == 0x10, + "Long '0x10' in base 0"); + testOk(epicsScanULong("0x10", &u, 0) && u == 0x10, + "ULong '0x10' in base 0"); + testOk(epicsScanLong("0x10", &l, 16) && l == 0x10, + "Long '0x10' in base 16"); + testOk(epicsScanULong("0x10", &u, 16) && u == 0x10, + "ULong '0x10' in base 16"); + testOk(epicsScanLong("10", &l, 16) && l == 0x10, + "Long '10' in base 16"); + testOk(epicsScanULong("10", &u, 16) && u == 0x10, + "ULong '10' in base 16"); + + testOk(epicsScanLong("0x7fffffff", &l, 0) && l == 0x7fffffff, + "Long '0x7fffffff'"); + testOk(epicsScanULong("0xffffffff", &u, 0) && u == 0xffffffff, + "ULong '0xffffffff'"); + testOk(epicsScanFloat("0xffffff", &f) && f == 0xffffff, + "Float '0xffffff'"); + testOk(epicsScanDouble("0xffffffff", &d) && d == 0xffffffff, + "Double '0xffffffff'"); + + testOk(epicsScanLong("-0x7fffffff", &l, 0) && l == -0x7fffffff, + "Long '-0x7fffffff'"); + testOk(epicsScanULong("-0x7fffffff", &u, 0) && u == -0x7fffffff, + "ULong '-0x7fffffff'"); + testOk(epicsScanFloat("-0xffffff", &f) && f == -0xffffff, + "Float '-0xffffff'"); + testOk(epicsScanDouble("-0x7fffffff", &d) && d == -0x7fffffff, + "Double '-0x7fffffff'"); + + testOk(!epicsParseInt8("0x7f", &i8, 0) && i8 == 0x7f, + "Int8 '0x7f'"); + testOk(!epicsParseInt8("-0x80", &i8, 0) && i8 == -0x80, + "Int8 '-0x80'"); + testOk(!epicsParseUInt8("0xff", &u8, 0) && u8 == 0xff, + "UInt8 '0xff'"); + testOk(epicsParseInt8("0x80", &i8, 0) == S_stdlib_overflow, + "Int8 '0x80' => overflow"); + testOk(epicsParseInt8("-0x81", &i8, 0) == S_stdlib_overflow, + "Int8 '-0x81' => overflow"); + testOk(epicsParseUInt8("0x100", &u8, 0) == S_stdlib_overflow, + "UInt8 '0x100' => overflow"); + + testOk(!epicsParseInt16("0x7fff", &i16, 0) && i16 == 0x7fff, + "Int16 '0x7fff'"); + testOk(!epicsParseInt16("-0x8000", &i16, 0) && i16 == -0x8000, + "Int16 '-0x8000'"); + testOk(!epicsParseUInt16("0xffff", &u16, 0) && u16 == 0xffff, + "UInt16 '0xffff'"); + testOk(epicsParseInt16("0x8000", &i16, 0) == S_stdlib_overflow, + "Int16 '0x8000' => overflow"); + testOk(epicsParseInt16("-0x8001", &i16, 0) == S_stdlib_overflow, + "Int16 '-0x8001' => overflow"); + testOk(epicsParseUInt16("0x10000", &u16, 0) == S_stdlib_overflow, + "UInt16 '0x10000' => overflow"); + + testOk(!epicsParseInt32("0x7fffffff", &i32, 0) && i32 == 0x7fffffff, + "Int32 '0x7fffffff'"); + testOk(!epicsParseInt32("-0x80000000", &i32, 0) && i32 == -0x80000000L, + "Int32 '-0x80000000'"); + testOk(!epicsParseUInt32("0xffffffff", &u32, 0) && u32 == 0xffffffff, + "UInt32 '0xffffffff'"); + testOk(epicsParseInt32("0x80000000", &i32, 0) == S_stdlib_overflow, + "Int32 '0x80000000' => overflow"); + testOk(epicsParseInt32("-0x80000001", &i32, 0) == S_stdlib_overflow, + "Int32 '-0x80000001' => overflow"); + testOk(epicsParseUInt32("0x100000000", &u32, 0) == S_stdlib_overflow, + "UInt32 '0x100000000' => overflow"); + + testOk(epicsScanFloat(".1", &f) && fabs(f - 0.1) < 1e-7, + "Float '.1'"); + testOk(epicsScanDouble(".1", &d) && fabs(d - 0.1) < 1e-15, + "Double '.1'"); + + testOk(epicsScanFloat("0.1", &f) && fabs(f - 0.1) < 1e-7, + "Float '0.1'"); + testOk(epicsScanDouble("0.1", &d) && fabs(d - 0.1) < 1e-15, + "Double '0.1'"); + + testOk(epicsScanFloat("1e-1", &f) && fabs(f - 1e-1) < 1e-7, + "Float '1e-1'"); + testOk(epicsScanDouble("1e-1", &d) && fabs(d - 1e-1) < 1e-15, + "Double '1e-1'"); + + testOk(epicsScanFloat("-.1", &f) && fabs(f + 0.1) < 1e-7, + "Float '-.1'"); + testOk(epicsScanDouble("-.1", &d) && fabs(d + 0.1) < 1e-15, + "Double '-.1'"); + + testOk(epicsScanFloat("-0.1", &f) && fabs(f + 0.1) < 1e-7, + "Float '-0.1'"); + testOk(epicsScanDouble("-0.1", &d) && fabs(d + 0.1) < 1e-15, + "Double '-0.1'"); + + testOk(epicsScanFloat("-1e-1", &f) && fabs(f + 1e-1) < 1e-7, + "Float '-1e-1'"); + testOk(epicsScanDouble("-1e-1", &d) && fabs(d + 1e-1) < 1e-15, + "Double '-1e-1'"); + + testOk(epicsScanFloat("1e-30", &f) && fabs(f - 1e-30) < 1e-36, + "Float '1e-30'"); + testOk(epicsScanDouble("1e-300", &d) && fabs(d - 1e-300) < 1e-306, + "Double '1e-300'"); + + testOk(epicsParseFloat("1e-40", &f) == S_stdlib_underflow, + "Float '1e-40' => underflow"); + testOk(epicsParseDouble("1e-330", &d) == S_stdlib_underflow, + "Double '1e-330' => underflow"); + + testOk(epicsScanFloat("1e30", &f) && fabs(f - 1e30) < 1e24, + "Float '1e30'"); + testOk(epicsScanDouble("1e300", &d) && d == 1e300, + "Double '1e300'"); + + testOk(epicsParseFloat("1e40", &f) == S_stdlib_overflow, + "Float '1e40' => overflow"); + testOk(epicsParseDouble("1e310", &d) == S_stdlib_overflow, + "Double '1e330' => overflow"); + + testOk(epicsScanLong("2147483647", &l, 0) && l == 2147483647, + "Long '2147483647'"); + testOk(epicsScanLong("-2147483647", &l, 0) && l == -2147483647, + "Long '-2147483647'"); + testOk(epicsScanULong("4294967295", &u, 0) && u == 4294967295u, + "ULong '4294967295'"); + testOk(epicsScanFloat("16777214", &f) && f == 16777214.0, + "Float '16777214'"); + testOk(epicsScanFloat("16777215", &f) && f == 16777215.0, + "Float '16777215'"); + testOk(epicsScanFloat("-16777215", &f) && f == -16777215.0, + "Float '-16777215'"); + testOk(epicsScanFloat("-16777216", &f) && f == -16777216.0, + "Float '-16777216'"); + testOk(epicsScanDouble("4294967294", &d) && d == 4294967294.0, + "Double '4294967294'"); + testOk(epicsScanDouble("4294967295", &d) && d == 4294967295.0, + "Double '4294967295'"); + testOk(epicsScanDouble("-4294967295", &d) && d == -4294967295.0, + "Double '-4294967295'"); + testOk(epicsScanDouble("-4294967296", &d) && d == -4294967296.0, + "Double '-4294967296'"); + + testOk(epicsScanFloat("NAN", &f) && isnan(f), "Float 'NAN'"); + testOk(epicsScanDouble("NAN", &d) && isnan(d), "Double 'NAN'"); + testOk(epicsScanFloat("Nan", &f) && isnan(f), "Float 'Nan'"); + testOk(epicsScanDouble("Nan", &d) && isnan(d), "Double 'Nan'"); + testOk(epicsScanFloat("nan()", &f) && isnan(f), "Float 'nan()'"); + testOk(epicsScanDouble("nan()", &d) && isnan(d), "Double 'nan()'"); + + testOk(epicsScanFloat("INF", &f) && f == epicsINF, + "Float 'INF'"); + testOk(epicsScanDouble("INF", &d) && d == epicsINF, + "Double 'INF'"); + testOk(epicsScanFloat("Infinity", &f) && f == epicsINF, + "Float 'Infinity'"); + testOk(epicsScanDouble("Infinity", &d) && d == epicsINF, + "Double 'Infinity'"); + + testOk(epicsScanFloat("+INF", &f) && f == epicsINF, + "Float '+INF'"); + testOk(epicsScanDouble("+INF", &d) && d == epicsINF, + "Double '+INF'"); + testOk(epicsScanFloat("+Infinity", &f) && f == epicsINF, + "Float '+Infinity'"); + testOk(epicsScanDouble("+Infinity", &d) && d == epicsINF, + "Double '+Infinity'"); + + testOk(epicsScanFloat("-INF", &f) && f == -epicsINF, + "Float '-INF'"); + testOk(epicsScanDouble("-INF", &d) && d == -epicsINF, + "Double '-INF'"); + testOk(epicsScanFloat("-Infinity", &f) && f == -epicsINF, + "Float '-Infinity'"); + testOk(epicsScanDouble("-Infinity", &d) && d == -epicsINF, + "Double '-Infinity'"); + +#ifdef epicsStrtod +#define CHECK_STRTOD epicsStrtod != strtod + if (epicsStrtod == strtod) + testDiag("This target defines epicsStrtod as strtod"); + else + testDiag("This target defines its own epicsStrtod macro"); +#else +#define CHECK_STRTOD 1 + testDiag("This target compiles epicsStrtod()"); +#endif + + if (CHECK_STRTOD) { + int pass = 0, fail = 0; + +#define CHECK(ok, msg) if (ok) pass++; else fail++, testDiag(msg) + + testDiag("Checking the native strtod(), only failures shown:"); + + CHECK(parseStrtod("", &d) == S_stdlib_noConversion, + " not ok - strtod('') => noConversion"); + CHECK(parseStrtod("\t \n", &d) == S_stdlib_noConversion, + " not ok - strtod('\\t 1\\n') => noConversion"); + CHECK(parseStrtod("!", &d) == S_stdlib_noConversion, + " not ok - strtod('!') => noConversion"); + CHECK(scanStrtod("0", &d) && d == 0, + " not ok - strtod('0')"); + CHECK(scanStrtod("\t 1\n", &d) && d == 1, + " not ok - strtod('\\t 1\\n')"); + CHECK(parseStrtod("2!", &d) == S_stdlib_extraneous, + " not ok - strtod('2!') => extraneous"); + CHECK(parseStrtod("3 !", &d) == S_stdlib_extraneous, + " not ok - strtod('3 !') => extraneous"); + CHECK(scanStrtod("0x0", &d) && d == 0, + " not ok - strtod('0x0')"); + CHECK(scanStrtod("0x1", &d) && d == 1, + " not ok - strtod('0x1')"); + CHECK(scanStrtod("+0x1", &d) && d == 1, + " not ok - strtod('+0x1')"); + CHECK(scanStrtod("-0x1", &d) && d == -1, + " not ok - strtod('-0x1')"); + CHECK(scanStrtod("0xf", &d) && d == 15, + " not ok - strtod('0xf')"); + CHECK(scanStrtod("0XF", &d) && d == 15, + " not ok - strtod('0XF')"); + CHECK(scanStrtod("0xffffffff", &d) && d == 0xffffffff, + " not ok - strtod('0xffffffff')"); + CHECK(scanStrtod("-0x7fffffff", &d) && d == -0x7fffffffl, + " not ok - strtod('-0x7fffffff')"); + CHECK(scanStrtod(".1", &d) && fabs(d - 0.1) < 1e-15, + " not ok - strtod('.1')"); + CHECK(scanStrtod("0.1", &d) && fabs(d - 0.1) < 1e-15, + " not ok - strtod('0.1')"); + CHECK(scanStrtod("1e-1", &d) && fabs(d - 1e-1) < 1e-15, + " not ok - strtod('1e-1')"); + CHECK(scanStrtod("-.1", &d) && fabs(d + 0.1) < 1e-15, + " not ok - strtod('-.1')"); + CHECK(scanStrtod("-0.1", &d) && fabs(d + 0.1) < 1e-15, + " not ok - strtod('-0.1')"); + CHECK(scanStrtod("-1e-1", &d) && fabs(d + 1e-1) < 1e-15, + " not ok - strtod('-1e-1')"); + CHECK(scanStrtod("1e-300", &d) && fabs(d - 1e-300) < 1e-306, + " not ok - strtod('1e-300')"); + CHECK(parseStrtod("1e-400", &d) == S_stdlib_underflow, + " not ok - strtod('1e-400') => underflow"); + CHECK(scanStrtod("4294967294", &d) && d == 4294967294.0, + " not ok - strtod('4294967294')"); + CHECK(scanStrtod("4294967295", &d) && d == 4294967295.0, + " not ok - strtod('4294967295')"); + CHECK(scanStrtod("-4294967295", &d) && d == -4294967295.0, + " not ok - strtod('-4294967295')"); + CHECK(scanStrtod("-4294967296", &d) && d == -4294967296.0, + " not ok - strtod('-4294967296')"); + CHECK(scanStrtod("NAN", &d) && isnan(d), + " not ok - strtod('NAN')"); + CHECK(scanStrtod("Nan", &d) && isnan(d), + " not ok - strtod('Nan')"); + CHECK(scanStrtod("nan()", &d) && isnan(d), + " not ok - strtod('nan()')"); + CHECK(scanStrtod("INF", &d) && d == epicsINF, + " not ok - strtod('INF')"); + CHECK(scanStrtod("Infinity", &d) && d == epicsINF, + " not ok - strtod('Infinity')"); + CHECK(scanStrtod("+INF", &d) && d == epicsINF, + " not ok - strtod('+INF')"); + CHECK(scanStrtod("+Infinity", &d) && d == epicsINF, + " not ok - strtod('+Infinity')"); + CHECK(scanStrtod("-INF", &d) && d == -epicsINF, + " not ok - strtod('-INF')"); + CHECK(scanStrtod("-Infinity", &d) && d == -epicsINF, + " not ok - strtod('-Infinity')"); + + if (fail) + testDiag("The native strtod() passed %d and failed %d tests", + pass, fail); + else { + testDiag("The native strtod() passed all %d tests", pass); +#ifndef epicsStrtod + testDiag("The compiled epicsStrtod() should not be needed"); +#endif + } + } + + return testDone(); +}