How do i verify a string is valid double (even if

2019-02-16 14:00发布

问题:

I have been up all night searching for a way to determine if my string value is a valid double and I haven't found a way that will also not reject a number with a point in it...

In my searches I found this

How to determine if a string is a number with C++?

and the answer that Charles Salvia gave was

bool is_number(const std::string& s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && std::isdigit(*it)) ++it;
return !s.empty() && it == s.end();
}

this works for any number that doesn't have a point in it but a number with a point gets rejected...

回答1:

You may be tempted to use std::stod like this:

bool is_number(const std::string& s)
{
    try
    {
        std::stod(s);
    }
    catch(...)
    {
        return false;
    }
    return true;
}

but this can be quite inefficient, see e.g. zero-cost exceptions.

So using @Robinson's solution or strtod is a better option:

bool is_number(const std::string& s)
{
    char* end = 0;
    double val = strtod(s.c_str(), &end);
    return end != s.c_str() && val != HUGE_VAL;
}


回答2:

Why not just use istringstream?

#include <sstream>

bool is_numeric (std::string const & str) 
{
    auto result = double();
    auto i = std::istringstream(str);

    i >> result;      
    i >> std::ws;  

    return !i.fail() && i.eof();
}


回答3:

You can also count how many points your string contains. If this number is less or equal than 1 and if all other characters are numbers, your string is a valid double.

bool isnumber(const string& s)
{
    int nb_point=0;
    for (int i=0; i<s.length();i++)
    {
         if (s[i]=='.')
         {
              nb_point++;
         }
         else if (!isdigit(s[i])
         {
              return false;
         }
    }
    if (nb_point<=1)
    {
          return true;
    }
    else
    {
          return false;
    }
}

You can also use a regex if you know how to deal with that...



回答4:

You could use std::istringstream(). It tells you if there is any non-numerics following the digits by not setting the eof() flag.

bool is_number(const std::string& s)
{
    long double ld;
    return((std::istringstream(s) >> ld >> std::ws).eof());
}

int main()
{
    std::cout << std::boolalpha << is_number("   3.14") << '\n';
    std::cout << std::boolalpha << is_number("   3.14x") << '\n';
    std::cout << std::boolalpha << is_number("   3.14 ") << '\n';
}

Output:

true
false
true

Templated Version: For testing specific types

template<typename Numeric>
bool is_number(const std::string& s)
{
    Numeric n;
    return((std::istringstream(s) >> n >> std::ws).eof());
}

int main()
{
    std::cout << std::boolalpha << is_number<int>("314") << '\n';
    std::cout << std::boolalpha << is_number<int>("3.14") << '\n';
    std::cout << std::boolalpha << is_number<float>("   3.14") << '\n';
    std::cout << std::boolalpha << is_number<double>("   3.14x") << '\n';
    std::cout << std::boolalpha << is_number<long double>("   3.14 ") << '\n';
}

Output:

true
false
true
false
true


回答5:

Making sure there is at most one dot in the number.

bool is_number(const std::string& s)
{
    if (s.empty())
       return false;

    bool sawDot = false;
    for (char c : s )
    {
       if ( !(std::isdigit(c) || (c == '.' && !sawDot) ) )
          return false;
       sawDot = sawDot || (c == '.');
    }

    return true;
}


回答6:

Add another check c == '.'.

bool is_number(const std::string& s)
{
    return !s.empty() && std::find_if(s.begin(), 
    s.end(), [](char c) { return !(std::isdigit(c) || c == '.');  }) == s.end();
}

You can make the code easier to read by using:

bool is_number(const std::string& s)
{
    int dotCount = 0;
    if (s.empty())
       return false;

    for (char c : s )
    {
       if ( !(std::isdigit(c) || c == '.' ) && dotCount > 1 )
       {
          return false;
       }
       dotCount += (c == '.');
    }

    return true;
}


回答7:

I am very strongly against using a try-catch approach for such a low level operation. Instead I am using myself the following:

bool const hasDoubleRepresentation( std::string const & str, double & d ) const {
std::stringstream sstr(str);
return !((sstr >> std::noskipws >> d).rdstate() ^ std::ios_base::eofbit);}

The above has the advantage that if it returns true, it also sets d to the number contained in str. If this additional functionality is not required, then one may remove the d as an input argument. Also note that the presence of noskipws (no skip whitespace) has the effect that it returns false if str is something like " 3.14 ", i.e. a number containing white space.



回答8:

There have been a few answers posted to this, so I figured I would add my own. If you have access to C++17, using charconv may possibly be a more efficient way. I have not benchmarked this, but for most string-conversion applications, charconv is very efficient. This solution also requires a compiler that supports charconv to/from a double. VC++ supports this; I'm not sure which other compilers have implemented this aspect of the standard library.

#include <string>
#include <charconv>

bool string_is_number(const std::string& str)
{
    if (str.empty()) return false;
    double result;
    auto[ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
    return (ec == std::errc());
}

Upon a successful conversion, std::from_chars returns std::errc(). This is in essence simply wrapping from_chars and discarding the resulting double. Depending on your use case, it might be more advantageous to take the error value and double (assuming the string will be converted to a number later on), but at that point it would make more sense to use use from_chars by itself.