views:

436

answers:

11

I'm having an issue with a program I'm working on in C++. I am asking the user to input a valid number. I take it in as a string because the particular assignment I'm doing, it makes it easier in the long run. For basic error checking, I want to check to see if the number entered is a valid number. Example:

Enter number: 3.14
This would be valid

Enter number: 3.1456.365.12
This shouldn't be valid

So my question is how do I this. I'm sort of new to c++ so I don't know all of the functions and classes there are, so any help will be much appreciated.

+7  A: 

I think boost::lexical_cast should help you here

Chris Card
Yes, it will throw a 'bad_lexical_cast' exception if it cannot be successfully converted. So make sure you always call it in a try/catch.
jeffamaphone
boost::lexical_cast is based on std::istringstream so it will not detect malformed input of the "3.14.15" kind.
Manuel
@Manuel: Actually it does. It also checks that the *entire* string was successfully converted. (Yes, stringstream allows you to check that too.) Not too sure, if that behavior is costumizable (one doesn't need that check always)...
UncleBens
+3  A: 

If you have no boost, you always can use strtod

Drakosha
A: 

Ah, I loved these assignments. A good old hand written lexer is the way to go (since you are still in the begining days -- don't try to use boost just yet). They are fast, easy to write and extremely fun to play with. If you can get a copy of Levine's book on Lex/Yacc, look up the first couple of chapters for ideas.

dirkgently
They're also easy to get wrong, particularly if you aren't quite clear on the permitted formats. Writing a few lexers is fun, but leave them out of production code.
David Thornley
I thought this was an assignment as in homework. Hm.
dirkgently
+3  A: 

You can use strtoX (where X is f for float, l for long, ul for unsigned long, etc.), choosing for the kind of number you want. One of the parameters you give it is an "end pointer", which points to the first character in the string that could not be converted into the target number type.

In your case, what you're apparently looking for is that the end pointer should be at the end of the string, indicating that all characters in the string were converted to the target type.

Edit: Sorry, didn't notice that you'd mentioned 'double' in the title (but not the question itself). That being the case, you'd use strtod, as a couple of others have also advised.

Jerry Coffin
+2  A: 

The best way is to make an actual attempt to convert your string to double using any of the standard and/or idiomatic ways to do the conversion, and check for errors afterwards. In C that would be functions from strto... group (which are, of course, perfectly usable in C++ as well). In C++ you can use stream-based conversion idiom.

One thing to watch for though is that the common convention in standard conversion methods is to convert "as much as possible" and not consider any extra characters as an error. For example, a string "123abc" is normally considered valid input, with only "123" part getting converted. All usable methods provide you with the way to detect the fact that there is something extra after the actual number, if you want to treat this situation as an error. But it is up to you to take the additional steps to perform this verification.

AndreyT
+7  A: 

use strtod, which converts a string to a double and returns any characters it couldn't interpret as part of the double.

double strtod(const char* nptr, char** endptr)

Like this:

char* input = "3.1456.365.12";
char* end;

strtod(input, &end);
if (*input == '\0')
{
  printf("fail due to empty string\n");
}
if (end == input || *end != '\0')
{
  printf("fail - the following characters are not part of a double\n%s\n", end);
}
Alex Brown
now tested, works.
Alex Brown
It can also fail due to under- and overflow which are not detected with this code. I think you'll also need to test for "inf" and check errno to be sure.
UncleBens
+1  A: 

A simple option is to use the sscanf function:

const char * num_as_str = "3.1416";
double num;

if(std::sscanf(num_as_str, "%lg", &num) == 1)
{ 
    std::cout << "You correctly entered the number " << num << "\n";
} 

If you want to get fancy you can use istringstream:

std::istringstream iss(num_as_str);
if(iss >> num)
{
    std::cout << "You correctly entered the number " << num << "\n";
}

If you want to get extra-fancy you can use boost's lexical_cast:

try
{
    num = boost::lexical_cast<double>(num_as_str);
}
catch(boost::bad_lexical_cast &)
{ 
    std::cout << "What you entered is not a proper number" << num << "\n";
}
Manuel
Your stringstream example is incorrect. His example of "3.1456.365.12" will report success.
luke
Ups, that's true. And the sscanf and lexical_cast solutions have the same problem. Looks like the correct solution is the one given by luke.
Manuel
+2  A: 

An example using only standard C++:

#include <sstream>

// ...

double dbl = 0.0;
std::istringstream num("3.1456.365.12");

num >> dbl;

if(!num.fail() &&
   num.eof()) // This second test is important! This makes sure that the entire string was converted to a number
{
    // success
}
else
{
    // failure
}

Bonus generic template function version:

#include <sstream>
#include <string>
#include <exception>

// Version that throws an exception on a bad parse:

template <typename T>
T parse(const std::string& str)
{
    T value;
    std::istringstream parser(str);
    parser >> value;

    if(!parser.fail() && parser.eof())
    {
        return value;
    }
    else
    {
        throw "bad lexical cast";
    }
}

// usage:

double foo = parse<double>("3.14234");

// Non-exception, error code version

template <typename T>
bool parse(const std::string& str, T& value)
{
    std::istringstream parser(str);
    parser >> value;

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

// usage:

double foo = 0.0;
bool success = parser<double>("3.11234", foo);
luke
shouldnt it be `num >> dbl;` instead of `num >> double;` in your first example ?..
smerlin
Yes, nice catch :)
luke
A: 

As mentioned by AndreyT, the best way is to attempt to convert the string into a float and check for an error. Personally I would opt to use std::istringstream, as you're using C++. Something like the following should work:

float ff;
std::istringstream istr;
std::string input("1234.5678");

// set the stream to have your string as its base
istr.str(input);

// now try to read the number:
istr >> ff;
if (istr.fail())
{
    // some error in the parsing
}

istringstream is part of STL, so you shouldn't need any additional libraries, and it will also with with exceptions if that's your choice. More information can be found here: http://www.cplusplus.com/reference/iostream/istringstream/

icabod
Just like Manuel's answer, your example will incorrectly report success on John's example of "3.1456.365.12"
luke
Yep, looks like this is a good example of C++ gotcha :)
Manuel
Yup :) It pays to test.
luke
Well, it was a quick attempt to do it in a C++ style without using what I would consider "legacy" calls.Besides, it would have come out in the testing. *cough*.
icabod
A: 

You could use regular expressions. Since you already have a string, it would be easy to compare that with this regex:

/^\d+(\.\d+)?$/

The library regex.h can help you here. See this: regex.h

Aaron
What about scientific notation? Checking that it actually fits into the double's range? (When you have a problem, regex is suggested. Now you have two problems. :) )
UncleBens
Who said anything about checking double's range or scientific notation? ;-) RegExes are great, but I admit that it is difficult to verify that they're correct.
Aaron
A: 

This is my quick hack :)

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

template <typename T>
bool fromStr(const std::string& str, T& var)
{
    std::stringstream convertor;
    convertor << str;
    convertor >> var;

    if( convertor.fail() )
        return false;

    char c = static_cast<char>( convertor.get() );

    return convertor.eof() || c == '\n' || c == ' ' || c == '\t';
}

int main()
{
    double d;
    std::string str = "5.04146.55";

    if( fromStr<double>(str, d) )
    {
        std::cout << "Valid conversion!, d = " << d;
    }
    else
    {
        std::cout << "Invalid conversion!";
    }   
}
AraK