# HG changeset patch # User Benjamin Golinvaux # Date 1574243262 -3600 # Node ID f68da12e852b9bcf4a8c748be4e093d55fc33d20 # Parent e2212644eab3e24255399595d82d4e3c051bd628 Added scientific notation support to StringToDouble diff -r e2212644eab3 -r f68da12e852b Framework/Toolbox/GenericToolbox.h --- a/Framework/Toolbox/GenericToolbox.h Fri Nov 08 14:39:00 2019 +0100 +++ b/Framework/Toolbox/GenericToolbox.h Wed Nov 20 10:47:42 2019 +0100 @@ -22,6 +22,8 @@ #include #include +#include + namespace OrthancStone { @@ -45,8 +47,25 @@ period++; ++p; } + else if (*p == 'e' || *p == 'E') + { + ++p; + if (*p == '-' || *p == '+') + ++p; + // "e+"/"E+" "e-"/"E-" or "e"/"E" must be followed by a number + if (!(*p >= '0' && *p <= '9')) + return false; + + // these must be the last in the string + while(*p >= '0' && *p <= '9') + ++p; + + return (*p == 0); + } else + { return false; + } } return true; } @@ -128,10 +147,62 @@ r += f; } r *= neg; - if (*p == 0 || (*p >= '0' && *p <= '9') ) + + // skip the remaining numbers until we reach not-a-digit (either the + // end of the string OR the scientific notation symbol) + while ((*p >= '0' && *p <= '9')) + ++p; + + if (*p == 0 ) + { return true; + } + else if ((*p == 'e') || (*p == 'E')) + { + // process the scientific notation + double sign; // no init is safe (read below) + ++p; + if (*p == '-') + { + sign = -1.0; + // point to first number + ++p; + } + else if (*p == '+') + { + sign = 1.0; + // point to first number + ++p; + } + else if (*p >= '0' && *p <= '9') + { + sign = 1.0; + } + else + { + // only a sign char or a number is allowed + return false; + } + // now p points to the absolute value of the exponent + double exp = 0; + while (*p >= '0' && *p <= '9') + { + exp = (exp * 10.0) + static_cast(*p - '0'); // 1 12 123 123 12345 + ++p; + } + // now we have our exponent. put a sign on it. + exp *= sign; + double scFac = ::pow(10.0, exp); + r *= scFac; + + // only allowed symbol here is EOS + return (*p == 0); + } else + { + // not allowed return false; + } } inline bool StringToDouble(double& r, const std::string& text) diff -r e2212644eab3 -r f68da12e852b UnitTestsSources/GenericToolboxTests.cpp --- a/UnitTestsSources/GenericToolboxTests.cpp Fri Nov 08 14:39:00 2019 +0100 +++ b/UnitTestsSources/GenericToolboxTests.cpp Wed Nov 20 10:47:42 2019 +0100 @@ -47,14 +47,16 @@ EXPECT_TRUE(LegitDoubleString("0.")); EXPECT_TRUE(LegitDoubleString(".0")); + EXPECT_TRUE(LegitDoubleString("1e-15")); + EXPECT_TRUE(LegitDoubleString("1E-15")); + EXPECT_TRUE(LegitDoubleString("0.31E-15")); + EXPECT_TRUE(LegitDoubleString(".0031E-15")); + EXPECT_TRUE(LegitDoubleString("1e-15")); + EXPECT_TRUE(LegitDoubleString("1E015")); + EXPECT_TRUE(LegitDoubleString("0.31E015")); + + EXPECT_FALSE(LegitDoubleString(".5f")); - EXPECT_FALSE(LegitDoubleString("1e-15")); - EXPECT_FALSE(LegitDoubleString("1E-15")); - EXPECT_FALSE(LegitDoubleString("0.31E-15")); - EXPECT_FALSE(LegitDoubleString(".0031E-15")); - EXPECT_FALSE(LegitDoubleString("1e-15")); - EXPECT_FALSE(LegitDoubleString("1E015")); - EXPECT_FALSE(LegitDoubleString("0.31E015")); EXPECT_FALSE(LegitDoubleString("\n.0031E015")); EXPECT_FALSE(LegitDoubleString(".05f")); EXPECT_FALSE(LegitDoubleString(" 1 2 ")); @@ -3838,6 +3840,119 @@ } } + +TEST(GenericToolbox, TestStringToDoubleHardScientific) +{ + using OrthancStone::GenericToolbox::StringToDouble; + const double TOLERANCE = 0.00000000000001; + + size_t i = 0; + const size_t COUNT = 125; + //const double FACTOR = 1.000000000171271211; + const double FACTOR = 1.71271211; + for (double b = DBL_EPSILON; b < DBL_MAX && i < COUNT; ++i, b *= FACTOR) + { + + // the tolerance must be adapted depending on the exponent + double exponent = (b == 0) ? 0 : 1.0 + std::floor(std::log10(std::fabs(b))); + double actualTolerance = TOLERANCE * pow(10.0, exponent); + + char txt[1024]; +#if defined(_MSC_VER) + sprintf_s(txt, "%.17e", b); +#else + snprintf(txt, sizeof(txt) - 1, "%.17e", b); +#endif + double r = 0.0; + bool ok = StringToDouble(r, txt); + +#if 0 + if (ok) + { + printf("OK for txt = \"%s\" and r = %.17e\n", txt, r); + } + else + { + printf("Not ok for txt = \"%s\" and r = %.17e\n", txt, r); + ok = StringToDouble(r, txt); + } +#endif + + EXPECT_TRUE(ok); + +#if 0 + if (fabs(b - r) > actualTolerance) + { + printf("NOK fabs(b (%.17f) - r (%.17f)) ((%.17f)) > actualTolerance (%.17f)\n", b, r, fabs(b - r), actualTolerance); + printf("NOK fabs(b (%.17e) - r (%.17e)) ((%.17e)) > actualTolerance (%.17e)\n", b, r, fabs(b - r), actualTolerance); + ok = StringToDouble(r, txt); + } + else + { + printf("OK fabs(b (%.17f) - r (%.17f)) ((%.17f)) <= actualTolerance (%.17f)\n", b, r, fabs(b - r), actualTolerance); + printf("OK fabs(b (%.17e) - r (%.17e)) ((%.17e)) <= actualTolerance (%.17e)\n", b, r, fabs(b - r), actualTolerance); + } +#endif + EXPECT_NEAR(b, r, actualTolerance); + } +} + +TEST(GenericToolbox, TestStringToDoubleHardNegScientific) +{ + using OrthancStone::GenericToolbox::StringToDouble; + const double TOLERANCE = 0.00000000000001; + + size_t i = 0; + const size_t COUNT = 125; + //const double FACTOR = 1.000000000171271211; + const double FACTOR = 1.71271211; + for (double b = -1.0 * DBL_EPSILON; b < DBL_MAX && i < COUNT; ++i, b *= FACTOR) + { + // the tolerance must be adapted depending on the exponent + double exponent = (b == 0) ? 0 : 1.0 + std::floor(std::log10(std::fabs(b))); + double actualTolerance = TOLERANCE * pow(10.0, exponent); + + char txt[1024]; +#if defined(_MSC_VER) + sprintf_s(txt, "%.17e", b); +#else + snprintf(txt, sizeof(txt) - 1, "%.17e", b); +#endif + double r = 0.0; + bool ok = StringToDouble(r, txt); + +#if 0 + if (ok) + { + printf("OK for txt = \"%s\" and r = %.17e\n", txt, r); + } + else + { + printf("Not ok for txt = \"%s\" and r = %.17e\n", txt, r); + ok = StringToDouble(r, txt); + } +#endif + + EXPECT_TRUE(ok); + +#if 0 + if (fabs(b - r) > actualTolerance) + { + printf("NOK fabs(b (%.17f) - r (%.17f)) ((%.17f)) > actualTolerance (%.17f)\n", b, r, fabs(b - r), actualTolerance); + printf("NOK fabs(b (%.17e) - r (%.17e)) ((%.17e)) > actualTolerance (%.17e)\n", b, r, fabs(b - r), actualTolerance); + ok = StringToDouble(r, txt); + } + else + { + printf("OK fabs(b (%.17f) - r (%.17f)) ((%.17f)) <= actualTolerance (%.17f)\n", b, r, fabs(b - r), actualTolerance); + printf("OK fabs(b (%.17e) - r (%.17e)) ((%.17e)) <= actualTolerance (%.17e)\n", b, r, fabs(b - r), actualTolerance); + } +#endif + EXPECT_NEAR(b, r, actualTolerance); + } +} + + TEST(GenericToolbox, TestStringToIntegerHard) { using OrthancStone::GenericToolbox::StringToInteger;