Hon Wi Cong
Hon Wi Cong

Reputation: 151

Only accepting numeric input from user

I am currently trying to accept only numeric input from the user in my program. I have done a lot of research but all the example code given can still accept the input such as 10abc.

This is a feet to meter converter.

include <iostream>
using namespace std;

bool isNumber(string feet){
    for (int i = 0; i < feet.length(); i++){
        if (isdigit(feet[i]) == false){
            return false;
        }
    return true;
    }
}

int main(){
    string feet;
    beginning:
    cout << "Enter the feet: ";
    cin >> feet;

    if (isNumber(feet))
        cout << "The meter is: " << (double)stod(feet) * 0.3048 << endl;
    else {
        cout << "Invalid input. Please try again" << endl;
        goto beginning;
    }
    return 0;
}

This code is close to perfect but it doesn't accept decimal point (speechless face) because decimal point will ruin "isdigit" function.

So, is there any method that only accept pure numeric and also include decimal point?

Upvotes: 3

Views: 733

Answers (2)

Andreas Wenzel
Andreas Wenzel

Reputation: 25385

So, is there any method that only accept pure numeric and also include decimal point?

The function std::strspn will return the length of the string that contains only characters that are found in another string. So, if you want to allow both decimal digits and decimal points, you could use the following for input validation:

bool isNumber( string feet )
{
    return strspn( feet.c_str(), "0123456789." ) == feet.length();
}

However, as already pointed out in the comments section, the conversion function std::stod itself provides information that can be used for input validation. If you provide a second parameter to that function, it will write to the provided variable the number of characters that were matched.

The function std::stod automatically consumes all leading whitespace characters (e.g. space and tab characters) before converting the value. If you also want to also allow trailing whitespace characters, but no other types of characters, then you may want to use the following code:

std::size pos;
double d;

//attempt the conversion
d = stod( feet, &pos );

//make sure that at least one character was converted
if ( pos == 0 )
    throw "Input was invalid!";

//make sure that all remaining characters are whitespace
while ( feet[pos] != '\0' )
{
    if ( !isspace( (unsigned char)feet[pos] ) )
        throw "Input was invalid!";
    pos++;
}

If you don't want to allow any whitespace at all, including leading whitespace, then you will have to validate the string contents yourself before passing it to std::stod.

You are probably wondering why I am casting the char to unsigned char. See this question for an explanation of the reason why this is necessary. For the same reason, the following line in your code is wrong:

if (isdigit(feet[i]) == false){

It should be changed to:

if (isdigit((unsigned char)feet[i]) == false){

Also, the following problems in your code seem worth mentioning:


The line

cin >> feet;

will read a single word of input. So if the user enters for example 2318 sijslnej, then that line of code will only write 2318 into feet. I doubt that this is what you want. You may want to use feet.getline instead, as that will read the entire line instead of only the first word.


I strongly suggest that you get out of the habit of writing this:

if ( isdigit(...) == false )

Although this line will always work, it is still a very bad habit, as this habit will also cause you to write the following:

if ( isdigit(...) == true )

This line is equivalent to

if ( isdigit(...) == 1 )

which is wrong. The function std::isdigit returns a value of type int, not bool, and the return value is not guaranteed to be 0 or 1. It can return any nonzero value to indicate a digit, for example 20. In that case, the if conditional expression mentioned above will be equivalent to 20 == 1, which will evaluate to false. This is not what you want.

Therefore, instead of

if ( isdigit(...) == false )

you should write

if ( !isdigit(...) )

and instead of

if ( isdigit(...) == true )

you should write:

if ( isdigit(...) )

Upvotes: 3

Marek R
Marek R

Reputation: 38161

template <typename T>
T readFromLine(std::istream& in)
{
    std::string line;
    while (std::getline(in, line)) { // read whole line until it is possible
        std::istringstream line_stream { line }; // initialize string stream to parse line read
        T x;

        // try read `x` then discard white spaces then check if end of lien was reached
        if (line_stream >> x >> std::ws && line_stream.eof()) {
            // stop since reading was successful and line do not contain anything extra
            return x;
        }
        // continue if reading `x` was unsuccessful or when line contains something extra
    }
    // while loop ended since end of file was reached or error on input stream
    throw std::invalid_argument { "stream to short" };
}

https://godbolt.org/z/75efEP9Y8

Upvotes: 0

Related Questions