changeset 1166:f68da12e852b

Added scientific notation support to StringToDouble
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 20 Nov 2019 10:47:42 +0100
parents e2212644eab3
children f2951ac074bd 1064098f496d bf08d28bc652
files Framework/Toolbox/GenericToolbox.h UnitTestsSources/GenericToolboxTests.cpp
diffstat 2 files changed, 194 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- 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 <string>
 #include <stdint.h>
+#include <math.h>
+
 
 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<double>(*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)
--- 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;