13

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...

Community
  • 1
  • 1

9 Answers9

15

You can use strtod:

#include <cstdlib>

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

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.

emlai
  • 37,861
  • 9
  • 87
  • 140
  • I like this one. std::stod can also throw out_of_range can't it, so you'll need another catch in there for that. – Robinson Mar 20 '15 at 14:59
  • True, I actually missed the part that the OP was looking for a "is valid double" behavior rather than "is valid number". It's fixed now, thanks! – emlai Mar 20 '15 at 15:06
  • What about efficiency? A false return is accompanied by the huge run time inefficiency associated with the catch handler. This ought to be a serious concern here since this function may be hit very frequently. – Yannis Dec 20 '18 at 21:45
  • Yeah, implementing a function like this with a catch is not the most efficient solution since most C++ implementations seem to use zero-cost exceptions. So using strtod or @Robinson should be preferred. – emlai Dec 21 '18 at 08:04
  • Even if the compiler makes no use of zero-cost exceptions, hitting the catch during run time means unwinding the stack. The associated cost is huge (I don't remember the exact multiplier). – Yannis Dec 21 '18 at 09:30
  • Dunno, the compiler might be able optimize the unwinding away since the exception is caught immediately. Depends a lot on the implementation. Anyway I updated the answer. – emlai Dec 21 '18 at 09:48
  • This code returns true on inputs like "800M." or "800M.87" when it should be false. – Ð.. Aug 07 '19 at 16:53
  • 1
    @Ð.. Fixed the solution so that it returns false for those. – emlai Mar 13 '20 at 14:27
5

Why not just use istringstream?

#include <sstream>

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

    i >> result;      

    return !i.fail() && i.eof();
}
Robinson
  • 8,836
  • 13
  • 64
  • 108
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
Galik
  • 42,526
  • 3
  • 76
  • 100
2

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...

fonfonx
  • 1,355
  • 17
  • 30
0

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;
}
R Sahu
  • 196,807
  • 13
  • 136
  • 247
  • @CinCout, yes, it will. Good catch. – R Sahu Apr 18 '18 at 15:22
  • Why not `return` within the loop when `dotCount > 1`? – CinCout Apr 18 '18 at 15:55
  • @CinCout, one could. It makes the code more complex than it needs to be for not much gain in actual usage if the number of times we are going to run into them is small compared to the number of times we are not going run into them. – R Sahu Apr 18 '18 at 15:57
  • I was doubtful if it'd be over optimization. Hence the question. – CinCout Apr 18 '18 at 15:58
0

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;
}
chmike
  • 17,648
  • 19
  • 66
  • 93
0

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.

Yannis
  • 123
  • 1
  • 5
0

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.

cameron
  • 55
  • 1
  • 5
0

Here is my solution:

bool isNumber(string s)
{
    for (int i = 0; i < s.length(); i++)
        if (isdigit(s[i]) == false && s[i] != '.')
            return false;

    return true;
}

It works properly for me, you can give it a try!