]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Use C++ facilities to convert numbers.
authorWolfgang Bangerth <bangerth@colostate.edu>
Tue, 11 Mar 2025 17:08:02 +0000 (11:08 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Tue, 11 Mar 2025 20:27:22 +0000 (14:27 -0600)
source/base/utilities.cc

index e7dd2d05de1fcfb9736fac63418ca36da3c9fa03..9f8b186db34b5906fdc4ec3b34ec9a6fe49af056 100644 (file)
@@ -43,7 +43,7 @@
 #include <algorithm>
 #include <bitset>
 #include <cctype>
-#include <cerrno>
+#include <charconv>
 #include <cmath>
 #include <cstddef>
 #include <cstdio>
@@ -597,33 +597,36 @@ namespace Utilities
   int
   string_to_int(const std::string &s_)
   {
-    // trim whitespace on either side of the text if necessary
+    // Trim whitespace on either side of the text if necessary
     std::string s = s_;
     while ((s.size() > 0) && (s[0] == ' '))
       s.erase(s.begin());
     while ((s.size() > 0) && (s.back() == ' '))
       s.erase(s.end() - 1);
 
-    // Now convert and see whether we succeed. Note that strtol only
-    // touches errno if an error occurred, so if we want to check
-    // whether an error happened, we need to make sure that errno==0
-    // before calling strtol since otherwise it may be that the
-    // conversion succeeds and that errno remains at the value it
-    // was before, whatever that was.
-    char *p;
-    errno       = 0;
-    const int i = std::strtol(s.c_str(), &p, 10);
+    // This function used to be built on top of strtol, which gladly converts
+    // "+1" into int(1). But the std::from_chars() function we use below that
+    // does not. So, if the string starts with a plus, just eat it so that we
+    // stay backward compatible:
+    if ((s.size() > 0) && (s[0] == '+'))
+      s.erase(s.begin());
+
+    // Now convert and see whether we succeed.
+    int                          i;
+    const std::from_chars_result result =
+      std::from_chars(s.data(), s.data() + s.size(), i, /*base=*/10);
 
     // We have an error if one of the following conditions is true:
-    // - strtol sets errno != 0
-    // - The original string was empty (we could have checked that
-    //   earlier already)
-    // - The string has non-zero length and strtol converted the
+    // - result.ec is not equal to a default-constructed std::errc(),
+    //   for example if the string does not even start with a
+    //   number; or
+    // - The string has non-zero length and std::from_chars() converted the
     //   first part to something useful, but stopped converting short
     //   of the terminating '\0' character. This happens, for example,
-    //   if the given string is "1234 abc".
-    AssertThrow(!((errno != 0) || (s.empty()) ||
-                  ((s.size() > 0) && (*p != '\0'))),
+    //   if the given string is "1234 abc". In that case,
+    //   result.ptr points to something other than the end of
+    //   the string (namely, the first character past the number).
+    AssertThrow(result.ec == std::errc() && result.ptr == s.data() + s.size(),
                 ExcMessage("Can't convert <" + s + "> to an integer."));
 
     return i;
@@ -652,26 +655,29 @@ namespace Utilities
     while ((s.size() > 0) && (s.back() == ' '))
       s.erase(s.end() - 1);
 
-    // Now convert and see whether we succeed. Note that strtol only
-    // touches errno if an error occurred, so if we want to check
-    // whether an error happened, we need to make sure that errno==0
-    // before calling strtol since otherwise it may be that the
-    // conversion succeeds and that errno remains at the value it
-    // was before, whatever that was.
-    char *p;
-    errno          = 0;
-    const double d = std::strtod(s.c_str(), &p);
+    // This function used to be built on top of strtod, which gladly converts
+    // "+1" into int(1). But the std::from_chars() function we use below that
+    // does not. So, if the string starts with a plus, just eat it so that we
+    // stay backward compatible:
+    if ((s.size() > 0) && (s[0] == '+'))
+      s.erase(s.begin());
+
+    // Now convert and see whether we succeed.
+    double                       d;
+    const std::from_chars_result result = std::from_chars(
+      s.data(), s.data() + s.size(), d, std::chars_format::general);
 
     // We have an error if one of the following conditions is true:
-    // - strtod sets errno != 0
-    // - The original string was empty (we could have checked that
-    //   earlier already)
-    // - The string has non-zero length and strtod converted the
+    // - result.ec is not equal to a default-constructed std::errc(),
+    //   for example if the string does not even start with a
+    //   number; or
+    // - The string has non-zero length and std::from_chars() converted the
     //   first part to something useful, but stopped converting short
     //   of the terminating '\0' character. This happens, for example,
-    //   if the given string is "1.234 abc".
-    AssertThrow(!((errno != 0) || (s.empty()) ||
-                  ((s.size() > 0) && (*p != '\0'))),
+    //   if the given string is "1234 abc". In that case,
+    //   result.ptr points to something other than the end of
+    //   the string (namely, the first character past the number).
+    AssertThrow(result.ec == std::errc() && result.ptr == s.data() + s.size(),
                 ExcMessage("Can't convert <" + s + "> to a double."));
 
     return d;

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.