1

I know this question has been asked before, but the answers have not solved my problem thus I ask this question

I have a simple program to find the greatest of three numbers which should accept only floating numbers. In case a character or string is entered an error needs to be displayed and the user needs to enter again.

I have a function to take a valid floating input

float validInput()
    {
        float x;
        cout<< flush;
        cin >> x;
        while(cin.fail())
        {
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(),'\n');
            cout << "Not a numeric value please enter again\n";
            cin >> x;
        }
        return x;
    }

so I take input in the main function using validInput like

int main()
{
   float x = validInput();
   float y = validInput();
   float z = validInput();
   findGreatest(x,y,z);
   return 0;
}

This method works for most inputs. It fails when I enter a number followed by a character the validInput function fails weirdly. On giving such an input it displays the error message "Not a numeric value please enter again" but does not take another input instead it considers the numeric value before the characters as the input and stores it. My requirement is to ignore the entire input and ask for a fresh input

From my understanding

cin.ignore(numeric_limits<streamsize>::max(),'\n');

does not ignore the numbers that are entered in the beginning of the input but only clears out the characters from the stream even though cin.fail() was true.

Is there any way to fix this? it probably needs different parameters for cin.ignore not sure though.

Thanks in advance

sources: https://stackoverflow.com/a/16934374/5236575

PS: I can't use any special libraries like boost. The code is for implementing test cases for software testing so it needs to handle any type of input correctly.

Community
  • 1
  • 1
shoaib30
  • 877
  • 11
  • 24

2 Answers2

1

std::cin will read sequentially from the stream, so if the first chars represent a valid number, it thinks it's OK. If you want to make sure what you read is indeed just a number (not followed by some additional chars), one option is to read using std::getline into a std::string, then convert the string to a float using std::stof (C++11). The latter takes as the second argument a pointer to the location of the first character that cannot be converted. If that location is smaller than the length of the string, then your line contains more than just the number, so the input is invalid.

Example:

#include <iostream>

int main()
{
    float x;
    std::size_t pos;
    std::string str;
    while (std::getline(std::cin, str))
    {
        try
        {
            x = std::stof(str, &pos);
        }
        catch(...)
        {
            std::cout << "Not a numeric value please enter again\n";
            continue;
        }
        if(pos != str.length())
        {
            std::cout << "Not a numeric value please enter again\n";
            continue;
        }
        break;
    }
    std::cout << x;
}
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • I get this error on entering a character `terminate called after throwing an instance of 'std::invalid_argument' what(): stof Aborted (core dumped)` am I doing something wrong? – shoaib30 Dec 05 '15 at 17:52
  • @ShoaibAhmed No, it means that the initial string cannot be converted at all, e.g. starts with a character. You can surround the line `x = std::stof...` by a `try/catch` block. See the updated edit. – vsoftco Dec 05 '15 at 17:54
1

You might want to look at what actually happens because the behavior isn't weird at all: when you enter a floating point number followed by a non-number on one line, the floating point value is extracted just OK. The failure case of your validInput() function is not hit - with that call! However, since there is something which can't be parsed as a floating point number when the function is called again the second call triggers the failure!

You can see that this is indeed the behavior by adding output between your validInput() calls. This way you can tell which of the calls actually produced the error. Storing the value when there is no error is the expected behavior!

You can make sure that a line doesn't contain invalid input by reading a line and verifying that you can read from the line and that there is nothing but space following on this line. For example:

float validInput()
{
    float x;
    // cout<< flush; There is *NO* reason to flush cout: it is flushed when using cin
    for (std::string line; std::getline(std::cin, line); ) {
        std::istringstream lin(line);
        if (lin >> x && (lin >> std::ws).eof()) {
            break;
        }
        cout << "Not a numeric value please enter again\n";
    }
    throw std::runtime_error("reached end of input before getting a valid value!");
    return x;
}

The use of lin >> std::ws skips all potential trailing whitespace (the newline is already removed by std::getline()) on this line. After that the stream should be consumed and the eof() flag is set if there is, indeed, no further input (it is one of the few valid uses of eof()...).

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Makes a lot of sense. But the code does not seem to work as after any numeric input on pressing enter the runtime_error is thrown, on commenting out the line `throw std::runtime_error` it works but displays "Not a numeric value please enter again" before taking input. i guess this is happening because there are other inputs before this floating input. – shoaib30 Dec 05 '15 at 17:23
  • The `return x;` should probably replace the `break`. – vsoftco Dec 05 '15 at 17:27
  • @vsoftco replacing `break` with `return x;` fixed the error thank you. But works fine when this function is the first to be called. but when i have other `std::cin` statements before the call of this function it prints _"Not a numeric value please enter again"_ and then takes input. – shoaib30 Dec 05 '15 at 17:36