1

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?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Hon Wi Cong
  • 131
  • 9
  • 1
    Do not use `using namespace std` as it is [bad practice](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice). – Mestkon Jul 27 '21 at 12:03
  • 1
    If you use it correctly, [`std::stod`](https://en.cppreference.com/w/cpp/string/basic_string/stof) will tell you where it stopped converting characters. If that's not at the end of the input string, then the input had extraneous character at the end. – Pete Becker Jul 27 '21 at 12:06
  • related/dupe: https://stackoverflow.com/questions/10828937/how-to-make-cin-take-only-numbers – NathanOliver Jul 27 '21 at 12:08
  • @Pete Becker yea my editor is showing me an error at "stod" just now and I understand now, thank you. – Hon Wi Cong Jul 27 '21 at 12:11
  • Side note: Writing `isdigit(feet[i]) == false` is dangerous. Although that expression will probably work, I don't recommend using it, because it implies that `isdigit(feet[i]) == true` will also work as intended. However, that expression won't work as intended, because `idigit` is not guaranteed to return either `0` or `1`, but it may also return any nonzero value (for example `20`) to indicate a digit. But the expression `20 == true` will evaluate to false. Therefore, I recommend that you never write `== false` or `== true` when using `isdigit`, as its return type is `int`, not `bool`. – Andreas Wenzel Jul 27 '21 at 12:15
  • @Andreas Wenzel thank you for your advice, I just move from Python to C++, everything seems much harder in C++. I will keep your advice in mind. – Hon Wi Cong Jul 27 '21 at 12:19
  • Just to clarify my previous comment: Writing `isdigit(feet[i]) == true` is equivalent to writing `isdigit(feet[i]) == 1`, which will fail if `isdigit` returns a nonzero value that is not `1`. Therefore, it is better to simply write `isdigit(feet[i])` without the `== true` at the end, as any nonzero expression will evaluate to true in an `if` statement. – Andreas Wenzel Jul 27 '21 at 12:30

2 Answers2

2

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

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0
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

Marek R
  • 32,568
  • 6
  • 55
  • 140