/*************************************************************************\
* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* SPDX-License-Identifier: EPICS
* 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 <float.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>

#include "epicsTypes.h"
#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, char **units)
{
    int c;
    char *endp;
    double value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = strtod(str, &endp);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == ERANGE)
        return (value == 0) ? S_stdlib_underflow : S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}
#define scanStrtod(str, to) !parseStrtod(str, to, NULL)


MAIN(epicsStdlibTest)
{
    unsigned long u;
    long l;
    unsigned long long ull;
    long long ll;
    double d;
    float f;
    char *endp;
    epicsInt8 i8;
    epicsUInt8 u8;
    epicsInt16 i16;
    epicsUInt16 u16;
    epicsInt32 i32;
    epicsUInt32 u32;
    epicsInt64 i64;
    epicsUInt64 u64;

    testPlan(199);

    testOk(epicsParseLong("", &l, 0, NULL) == S_stdlib_noConversion,
        "Long '' => noConversion");
    testOk(epicsParseULong("", &u, 0, NULL) == S_stdlib_noConversion,
        "ULong '' => noConversion");
    testOk(epicsParseLLong("", &ll, 0, NULL) == S_stdlib_noConversion,
        "LLong '' => noConversion");
    testOk(epicsParseULLong("", &ull, 0, NULL) == S_stdlib_noConversion,
        "ULLong '' => noConversion");
    testOk(epicsParseFloat("", &f, NULL) == S_stdlib_noConversion,
        "Float '' => noConversion");
    testOk(epicsParseDouble("", &d, NULL) == S_stdlib_noConversion,
        "Double '' => noConversion");

    testOk(epicsParseLong("\t \n", &l, 0, NULL) == S_stdlib_noConversion,
        "Long '\\t 1\\n' => noConversion");
    testOk(epicsParseULong("\t \n", &u, 0, NULL) == S_stdlib_noConversion,
        "ULong '\\t 1\\n' => noConversion");
    testOk(epicsParseLLong("\t \n", &ll, 0, NULL) == S_stdlib_noConversion,
        "LLong '\\t 1\\n' => noConversion");
    testOk(epicsParseULLong("\t \n", &ull, 0, NULL) == S_stdlib_noConversion,
        "ULLong '\\t 1\\n' => noConversion");
    testOk(epicsParseFloat("\t \n", &f, NULL) == S_stdlib_noConversion,
        "Float '\\t 1\\n' => noConversion");
    testOk(epicsParseDouble("\t \n", &d, NULL) == S_stdlib_noConversion,
        "Double '\\t 1\\n' => noConversion");

    testOk(epicsParseLong("!", &l, 0, NULL) == S_stdlib_noConversion,
        "Long '!' => noConversion");
    testOk(epicsParseULong("!", &u, 0, NULL) == S_stdlib_noConversion,
        "ULong '!' => noConversion");
    testOk(epicsParseLLong("!", &ll, 0, NULL) == S_stdlib_noConversion,
        "LLong '!' => noConversion");
    testOk(epicsParseULLong("!", &ull, 0, NULL) == S_stdlib_noConversion,
        "ULLong '!' => noConversion");
    testOk(epicsParseFloat("!", &f, NULL) == S_stdlib_noConversion,
        "Float '!' => noConversion");
    testOk(epicsParseDouble("!", &d, NULL) == S_stdlib_noConversion,
        "Double '!' => noConversion");

    testOk(epicsScanLong("0", &l, 0) && l == 0, "Long '0'");
    testOk(epicsScanULong("0", &u, 0) && u == 0, "ULong '0'");
    testOk(epicsScanLLong("0", &ll, 0) && ll == 0, "LLong '0'");
    testOk(epicsScanULLong("0", &ull, 0) && ull == 0, "ULLong '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(epicsScanLLong("\t 1\n", &ll, 0) && ll == 1, "LLong '\\t 1\\n'");
    testOk(epicsScanULLong("\t 1\n", &ull, 0) && ull == 1, "ULLong '\\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(epicsScanLong("-1", &l, 0) && l == -1, "Long '-1'");
    testOk(epicsScanULong("-1", &u, 0) && u + 1 == 0, "ULong '-1'");
    testOk(epicsScanLLong("-1", &ll, 0) && ll == -1, "LLong '-1'");
    testOk(epicsScanULLong("-1", &ull, 0) && ull + 1 == 0, "ULLong '-1'");
    testOk(epicsScanFloat("-1", &f) && f == -1, "Float '-1'");
    testOk(epicsScanDouble("-1", &d) && d == -1, "Double '-1'");

    testOk(epicsParseLong("2!", &l, 0, NULL) == S_stdlib_extraneous,
        "Long '2!' => extraneous");
    testOk(epicsParseULong("2!", &u, 0, NULL) == S_stdlib_extraneous,
        "ULong '2!' => extraneous");
    testOk(epicsParseLLong("2!", &ll, 0, NULL) == S_stdlib_extraneous,
        "LLong '2!' => extraneous");
    testOk(epicsParseULLong("2!", &ull, 0, NULL) == S_stdlib_extraneous,
        "ULLong '2!' => extraneous");
    testOk(epicsParseFloat("2!", &f, NULL) == S_stdlib_extraneous,
        "Float '2!' => extraneous");
    testOk(epicsParseDouble("2!", &d, NULL) == S_stdlib_extraneous,
        "Double '2!' => extraneous");

    testOk(epicsParseLong("3 \n\t!", &l, 0, NULL) == S_stdlib_extraneous,
        "Long '3 \\n\\t!' => extraneous");
    testOk(epicsParseULong("3 \n\t!", &u, 0, NULL) == S_stdlib_extraneous,
        "ULong '3 \\n\\t!' => extraneous");
    testOk(epicsParseLLong("3 \n\t!", &ll, 0, NULL) == S_stdlib_extraneous,
        "LLong '3 \\n\\t!' => extraneous");
    testOk(epicsParseULLong("3 \n\t!", &ull, 0, NULL) == S_stdlib_extraneous,
        "ULLong '3 \\n\\t!' => extraneous");
    testOk(epicsParseFloat("3 \n\t!", &f, NULL) == S_stdlib_extraneous,
        "Float '3 \\n\\t!' => extraneous");
    testOk(epicsParseDouble("3 \n\t!", &d, NULL) == S_stdlib_extraneous,
        "Double '3 \\n\\t!' => extraneous");

    testOk(!epicsParseLong("2!", &l, 0, &endp) && *endp == '!',
        "Long '2!' => units='!'");
    testOk(!epicsParseULong("2!", &u, 0, &endp) && *endp == '!',
        "ULong '2!' => units='!'");
    testOk(!epicsParseLLong("2!", &ll, 0, &endp) && *endp == '!',
        "LLong '2!' => units='!'");
    testOk(!epicsParseULLong("2!", &ull, 0, &endp) && *endp == '!',
        "ULLong '2!' => units='!'");
    testOk(!epicsParseFloat("2!", &f, &endp) && *endp == '!',
        "Float '2!' => units='!'");
    testOk(!epicsParseDouble("2!", &d, &endp) && *endp == '!',
        "Double '2!' => units='!'");

    testOk(!epicsParseLong("3 \n\t!", &l, 0, &endp) && *endp == '!',
        "Long '3 \\n\\t!' => units='!'");
    testOk(!epicsParseULong("3 \n\t!", &u, 0, &endp) && *endp == '!',
        "ULong '3 \\n\\t!' => units='!'");
    testOk(!epicsParseLLong("3 \n\t!", &ll, 0, &endp) && *endp == '!',
        "LLong '3 \\n\\t!' => units='!'");
    testOk(!epicsParseULLong("3 \n\t!", &ull, 0, &endp) && *endp == '!',
        "ULLong '3 \\n\\t!' => units='!'");
    testOk(!epicsParseFloat("3 \n\t!", &f, &endp) && *endp == '!',
        "Float '3 \\n\\t!' => units='!'");
    testOk(!epicsParseDouble("3 \n\t!", &d, &endp) && *endp == '!',
        "Double '3 \\n\\t!' => units='!'");

    testOk(epicsScanLong("0x0", &l, 0) && l == 0, "Long '0x0'");
    testOk(epicsScanULong("0x0", &u, 0) && u == 0, "ULong '0x0'");
    testOk(epicsScanLLong("0x0", &ll, 0) && ll == 0, "LLong '0x0'");
    testOk(epicsScanULLong("0x0", &ull, 0) && ull == 0, "ULLong '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(epicsScanLLong("0x1", &ll, 0) && ll == 1, "LLong '0x1'");
    testOk(epicsScanULLong("0x1", &ull, 0) && ull == 1, "ULLong '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(epicsScanLLong("+0x1", &ll, 0) && ll == 1, "LLong '+0x1'");
    testOk(epicsScanULLong("+0x1", &ull, 0) && ull == 1, "ULLong '+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(epicsScanLLong("-0x1", &ll, 0) && ll == -1, "LLong '-0x1'");
    testOk(epicsScanULLong("-0x1", &ull, 0) && ull == -1, "ULLong '-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(epicsScanLLong("0xf", &ll, 0) && ll == 15, "LLong '0xf'");
    testOk(epicsScanULLong("0xf", &ull, 0) && ull == 15, "ULLong '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(epicsScanLLong("0XF", &ll, 0) && ll == 15, "LLong '0XF'");
    testOk(epicsScanULLong("0XF", &ull, 0) && ull == 15, "ULLong '0XF'");
    testOk(epicsScanFloat("0XF", &f) && f == 15, "Float '0XF'");
    testOk(epicsScanDouble("0XF", &d) && d == 15, "Double '0XF'");

    testOk(epicsParseLong("0x0", &l, 10, NULL) == S_stdlib_extraneous,
        "Long '0x0' in base 10 => extraneous");
    testOk(epicsParseULong("0x0", &u, 10, NULL) == S_stdlib_extraneous,
        "ULong '0x0' in base 10 => extraneous");
    testOk(epicsParseLLong("0x0", &ll, 10, NULL) == S_stdlib_extraneous,
        "LLong '0x0' in base 10 => extraneous");
    testOk(epicsParseULLong("0x0", &ull, 10, NULL) == S_stdlib_extraneous,
        "ULLong '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(epicsScanLLong("0x7fffffffffffffff", &ll, 0) &&
        ll == 0x7fffffffffffffffLL, "LLong '0x7fffffffffffffff'");
    testOk(epicsScanULLong("0xffffffffffffffff", &ull, 0) &&
        ull == 0xffffffffffffffffULL, "ULLong '0xffffffffffffffff'");
    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(epicsScanLLong("-0x7fffffffffffffff", &ll, 0)
        && ll == -0x7fffffffffffffffLL, "LLong '-0x7fffffffffffffff'");
    testOk(epicsScanULLong("-0x7fffffffffffffff", &ull, 0) &&
        ull == -0x7fffffffffffffffULL, "ULLong '-0x7fffffffffffffff'");
    testOk(epicsScanFloat("-0xffffff", &f) && f == -0xffffff,
        "Float '-0xffffff'");
    testOk(epicsScanDouble("-0x7fffffff", &d) && d == -0x7fffffff,
        "Double '-0x7fffffff'");

    testOk(!epicsParseInt8("0x7f", &i8, 0, NULL) && i8 == 0x7f,
        "Int8 '0x7f'");
    testOk(!epicsParseInt8("-0x80", &i8, 0, NULL) && ((i8 + 0x80) & 0xff) == 0,
        "Int8 '-0x80'");
    testOk(!epicsParseUInt8("0xff", &u8, 0, NULL) && u8 == 0xff,
        "UInt8 '0xff'");
    testOk(!epicsParseUInt8("-1", &u8, 0, NULL) && u8 == 0xff,
        "UInt8 '-1'");
    testOk(epicsParseInt8("0x80", &i8, 0, NULL) == S_stdlib_overflow,
        "Int8 '0x80' => overflow");
    testOk(epicsParseInt8("-0x81", &i8, 0, NULL) == S_stdlib_overflow,
        "Int8 '-0x81' => overflow");
    testOk(epicsParseUInt8("0x100", &u8, 0, NULL) == S_stdlib_overflow,
        "UInt8 '0x100' => overflow");
    testOk(epicsParseUInt8("-0x100", &u8, 0, NULL) == S_stdlib_overflow,
        "UInt8 '-0x100' => overflow");

    testOk(!epicsParseInt16("0x7fff", &i16, 0, NULL) && i16 == 0x7fff,
        "Int16 '0x7fff'");
    testOk(!epicsParseInt16("-0x8000", &i16, 0, NULL) && i16 == -0x8000,
        "Int16 '-0x8000'");
    testOk(!epicsParseUInt16("0xffff", &u16, 0, NULL) && u16 == 0xffff,
        "UInt16 '0xffff'");
    testOk(!epicsParseUInt16("-1", &u16, 0, NULL) && u16 == 0xffff,
        "UInt16 '-1'");
    testOk(epicsParseInt16("0x8000", &i16, 0, NULL) == S_stdlib_overflow,
        "Int16 '0x8000' => overflow");
    testOk(epicsParseInt16("-0x8001", &i16, 0, NULL) == S_stdlib_overflow,
        "Int16 '-0x8001' => overflow");
    testOk(epicsParseUInt16("0x10000", &u16, 0, NULL) == S_stdlib_overflow,
        "UInt16 '0x10000' => overflow");
    testOk(epicsParseUInt16("-0x10000", &u16, 0, NULL) == S_stdlib_overflow,
        "UInt16 '-0x10000' => overflow");

    testOk(!epicsParseInt32("0x7fffffff", &i32, 0, NULL) && i32 == 0x7fffffff,
        "Int32 '0x7fffffff'");
    testOk(!epicsParseInt32("-0x80000000", &i32, 0, NULL) && i32 == -0x80000000L,
        "Int32 '-0x80000000'");
    testOk(!epicsParseUInt32("0xffffffff", &u32, 0, NULL) && u32 == 0xffffffff,
        "UInt32 '0xffffffff'");
    testOk(!epicsParseUInt32("-1", &u32, 0, NULL) && u32 == 0xffffffffU,
        "UInt32 '-1'");
    testOk(epicsParseInt32("0x80000000", &i32, 0, NULL) == S_stdlib_overflow,
        "Int32 '0x80000000' => overflow");
    testOk(epicsParseInt32("-0x80000001", &i32, 0, NULL) == S_stdlib_overflow,
        "Int32 '-0x80000001' => overflow");
    testOk(epicsParseUInt32("0x100000000", &u32, 0, NULL) == S_stdlib_overflow,
        "UInt32 '0x100000000' => overflow");
    testOk(epicsParseUInt32("-0x100000000", &u32, 0, NULL) == S_stdlib_overflow,
        "UInt32 '-0x100000000' => overflow");

    testOk(!epicsParseInt64("0x7fffffffffffffff", &i64, 0, NULL) &&
        i64 == 0x7fffffffffffffffLL, "Int64 '0x7fffffffffffffff'");
    testOk(!epicsParseInt64("-0x8000000000000000", &i64, 0, NULL) &&
        i64 == -0x8000000000000000LL, "Int64 '-0x8000000000000000'");
    testOk(!epicsParseUInt64("0xffffffffffffffff", &u64, 0, NULL) &&
        u64 == 0xffffffffffffffffULL, "UInt64 '0xffffffffffffffff'");
    testOk(!epicsParseUInt64("-1", &u64, 0, NULL) && u64 == 0xffffffffffffffffULL,
        "UInt64 '-1'");
    testOk(epicsParseInt64("0x8000000000000000", &i64, 0, NULL) == S_stdlib_overflow,
        "Int64 '0x8000000000000000' => overflow");
    testOk(epicsParseInt64("-0x8000000000000001", &i64, 0, NULL) == S_stdlib_overflow,
        "Int64 '-0x8000000000000001' => overflow");
    testOk(epicsParseUInt64("0x10000000000000000", &u64, 0, NULL) == S_stdlib_overflow,
        "UInt64 '0x10000000000000000' => overflow");
    testOk(epicsParseUInt64("-0x10000000000000000", &u64, 0, NULL) == S_stdlib_overflow,
        "UInt64 '-0x10000000000000000' => 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, NULL) == S_stdlib_underflow,
        "Float '1e-40' => underflow");
#ifdef vxWorks
    testTodoBegin("Not detected on VxWorks");
#endif
    testOk(epicsParseDouble("1e-330", &d, NULL) == S_stdlib_underflow,
        "Double '1e-330' => underflow");
#ifdef vxWorks
    testTodoEnd();
#endif

    testOk(epicsScanFloat("1e30", &f) && fabs(f - 1e30) < 1e24,
        "Float '1e30'");
    testOk(epicsScanDouble("1e300", &d) && fabs(d - 1e300) < 1e285,
        "Double '1e300'");

    testOk(epicsParseFloat("1e40", &f, NULL) == S_stdlib_overflow,
        "Float '1e40' => overflow");
    testOk(epicsParseDouble("1e310", &d, NULL) == 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'");
#ifdef __rtems__
    testTodoBegin("RTEMS (newlib) parser doesn't recognise 'nan()'");
#endif
    testOk(epicsScanFloat("nan()", &f) && isnan(f), "Float 'nan()'");
    testOk(epicsScanDouble("nan()", &d) && isnan(d), "Double 'nan()'");
#ifdef __rtems__
    testTodoEnd();
#endif

    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, NULL) == S_stdlib_noConversion,
            "   not ok - strtod('') => noConversion");
        CHECK(parseStrtod("\t \n", &d, NULL) == S_stdlib_noConversion,
            "   not ok - strtod('\\t 1\\n') => noConversion");
        CHECK(parseStrtod("!", &d, NULL) == 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, NULL) == S_stdlib_extraneous,
            "   not ok - strtod('2!') => extraneous");
        CHECK(parseStrtod("3 !", &d, NULL) == S_stdlib_extraneous,
            "   not ok - strtod('3 !') => extraneous");
        CHECK(!parseStrtod("2!", &d, &endp) && *endp == '!',
            "   not ok - strtod('2!') => units='!'");
        CHECK(!parseStrtod("3 \n\t!", &d, &endp) && *endp == '!',
            "   not ok - strtod('3 \\n\\t!') => units='!'");
        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, NULL) == 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();
}
