From c1c4e91ffb50a68dd9cf8654d52c8813fd7ad70a Mon Sep 17 00:00:00 2001 From: Mark Olesen Date: Wed, 25 Oct 2017 20:55:37 +0200 Subject: [PATCH] ENH: handle underflow (rounding) of float/double as zero (issue #625) - The problem occurs when using atof to parse values such as "1e-39" since this is out of range for a float and _can_ set errno to ERANGE. Similar to parsing of integers, now parse with the longest floating point representation "long double" via strtold (guaranteed to be part of C++11) and verify against the respective VGREAT values for overflow. Treat anything smaller than VSMALL to be zero. --- .../test/primitives/Test-primitives.C | 23 ++++++++++--- src/OpenFOAM/primitives/Scalar/Scalar.C | 33 ++++++++++++++++--- .../Scalar/doubleScalar/doubleScalar.C | 4 ++- .../Scalar/floatScalar/floatScalar.C | 3 +- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/applications/test/primitives/Test-primitives.C b/applications/test/primitives/Test-primitives.C index 219b1ef5ec..acdb71fb05 100644 --- a/applications/test/primitives/Test-primitives.C +++ b/applications/test/primitives/Test-primitives.C @@ -118,7 +118,8 @@ int main(int argc, char *argv[]) unsigned nFail = 0; { - Info<< nl << "Test readDouble:" << nl; + Info<< nl << "Test readDouble: (small=" << doubleScalarVSMALL + << " great=" << doubleScalarVSMALL << "):" << nl; nFail += testParsing ( &readDouble, @@ -131,12 +132,18 @@ int main(int argc, char *argv[]) { " 3.14159 ", true }, { " 31.4159E-1 " , true }, { " 100E1000 " , false }, + { " 1E-40 " , true }, + { " 1E-305 " , true }, + { " 1E-37 " , true }, + { " 1E-300 " , true }, } ); } { - Info<< nl << "Test readFloat:" << nl; + Info<< nl << "Test readFloat: (small=" << floatScalarVSMALL + << " great=" << floatScalarVGREAT << "):" << nl; + nFail += testParsing ( &readFloat, @@ -145,6 +152,10 @@ int main(int argc, char *argv[]) { " 31.4159E-1 " , true }, { " 31.4159E200 " , false }, { " 31.4159E20 " , true }, + { " 1E-40 " , true }, + { " 1E-305 " , true }, + { " 1E-37 " , true }, + { " 1E-300 " , true }, } ); } @@ -160,12 +171,16 @@ int main(int argc, char *argv[]) { " 314.159-2 " , true }, { " 31.4159E200 " , true }, { " 31.4159E20 " , true }, + { " 1E-40 " , true }, + { " 1E-305 " , true }, + { " 1E-37 " , true }, + { " 1E-300 " , true }, } ); } { - Info<< nl << "Test readInt32 (max= " << INT32_MAX << "):" << nl; + Info<< nl << "Test readInt32 (max=" << INT32_MAX << "):" << nl; nFail += testParsing ( &readInt32, @@ -181,7 +196,7 @@ int main(int argc, char *argv[]) } { - Info<< nl << "Test readUint32 (max= " + Info<< nl << "Test readUint32 (max=" << unsigned(UINT32_MAX) << "):" << nl; nFail += testParsing ( diff --git a/src/OpenFOAM/primitives/Scalar/Scalar.C b/src/OpenFOAM/primitives/Scalar/Scalar.C index 644e9abf12..d94c3c31c4 100644 --- a/src/OpenFOAM/primitives/Scalar/Scalar.C +++ b/src/OpenFOAM/primitives/Scalar/Scalar.C @@ -80,10 +80,15 @@ Scalar ScalarRead(const char* buf) { char* endptr = nullptr; errno = 0; + const auto parsed = ScalarConvert(buf, &endptr); - const Scalar val = ScalarConvert(buf, &endptr); + const parsing::errorType err = + ( + (parsed < -ScalarVGREAT || parsed > ScalarVGREAT) + ? parsing::errorType::RANGE + : parsing::checkConversion(buf, endptr) + ); - const parsing::errorType err = parsing::checkConversion(buf, endptr); if (err != parsing::errorType::NONE) { FatalIOErrorInFunction("unknown") @@ -91,7 +96,13 @@ Scalar ScalarRead(const char* buf) << exit(FatalIOError); } - return val; + // Round underflow to zero + return + ( + (parsed > -ScalarVSMALL && parsed < ScalarVSMALL) + ? 0 + : Scalar(parsed) + ); } @@ -99,10 +110,22 @@ bool readScalar(const char* buf, Scalar& val) { char* endptr = nullptr; errno = 0; + const auto parsed = ScalarConvert(buf, &endptr); - val = ScalarConvert(buf, &endptr); + // Round underflow to zero + val = + ( + (parsed > -ScalarVSMALL && parsed < ScalarVSMALL) + ? 0 + : Scalar(parsed) + ); - return (parsing::checkConversion(buf, endptr) == parsing::errorType::NONE); + return + ( + (parsed < -ScalarVGREAT || parsed > ScalarVGREAT) + ? false + : (parsing::checkConversion(buf, endptr) == parsing::errorType::NONE) + ); } diff --git a/src/OpenFOAM/primitives/Scalar/doubleScalar/doubleScalar.C b/src/OpenFOAM/primitives/Scalar/doubleScalar/doubleScalar.C index da73874c63..41069b3555 100644 --- a/src/OpenFOAM/primitives/Scalar/doubleScalar/doubleScalar.C +++ b/src/OpenFOAM/primitives/Scalar/doubleScalar/doubleScalar.C @@ -28,6 +28,7 @@ License #include "parsing.H" #include "IOstreams.H" +#include #include // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // @@ -40,7 +41,8 @@ License #define ScalarROOTVGREAT doubleScalarROOTVGREAT #define ScalarROOTVSMALL doubleScalarROOTVSMALL #define ScalarRead readDouble -#define ScalarConvert ::strtod +// Convert using larger representation to properly capture underflow +#define ScalarConvert ::strtold #include "Scalar.C" diff --git a/src/OpenFOAM/primitives/Scalar/floatScalar/floatScalar.C b/src/OpenFOAM/primitives/Scalar/floatScalar/floatScalar.C index 94d40441f7..5a8e071556 100644 --- a/src/OpenFOAM/primitives/Scalar/floatScalar/floatScalar.C +++ b/src/OpenFOAM/primitives/Scalar/floatScalar/floatScalar.C @@ -40,7 +40,8 @@ License #define ScalarROOTVGREAT floatScalarROOTVGREAT #define ScalarROOTVSMALL floatScalarROOTVSMALL #define ScalarRead readFloat -#define ScalarConvert ::strtof +// Convert using larger representation to properly capture underflow +#define ScalarConvert ::strtod #include "Scalar.C"