Search code examples
c++error-checking

Don't accept decimals as an input


I had to write a program that will ask the user for a number and if they enter zero, it will print out that they entered zero, if they enter a negative number or positive number, it will print out that they entered either a negative or positive number. I have it so it doesn't accept letters, and commas and such. But i can't figure out how to get this to not accept decimals? Any clues how i can do this? Any good sites with good c++ references other than cplusplus.com

#include <iostream>
#include <string>
#include <limits>
#include <cmath>
#include <iomanip>
#include <cstdlib>

using namespace std;

    int getInt()
    {
    int choice=0;
    while (!(cin >> choice))
        {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(),'\n');
        cout << "Please input a valid integer: " << '\n';
        }
    return (choice);
    }

int print_zero()
{
 cout << "The number you entered is a zero. " << '\n';
 return 0;
}

int print_negative()
{
 cout << "You entered a negative number. " << '\n';
 return 0;
}

int print_positive()
{
    cout << "You entered a positive number. " << '\n';
    return 0;
}

int main ()
    {

    cout << "your number please:-" << '\n';
    int choice = getInt();

    if (choice == 0)
    {
        print_zero();
    }

    if (choice < 0)
    {
        print_negative();
    }

    if (choice > 0)
    {
        print_positive();
    }

cout << endl << "All done! Nice!!" << endl;

return 0;
}

Solution

  • In addition to the previous answer, you also have the option of creating your own std::num_get<char> facet to seamlessly integrate your custom input parsing into the IOStreams interface.

    If you happened to enter a floating-point literal while reading into an integer, the stream will still parse as many characters as it can, as long as those characters can be used in the data type to which you are extracting. When the stream finds the end of the stream, a whitespace character, or a character that doesn't meet the formatting requirements for the type, it is only then that it will stop reading. In our case the stream will find the character . and then stop reading.

    The result is that the read is considered successful even though part of the input has been consumed. The next read however will be unsuccessful because the next character is a ., which is unusable in an integer.

    This is information is what we will use to customize our facet. We simply have to check if, after the input is read, that the next character is a decimal point. And f it is, you have a couple of options to report the error:

    You can...

    • Output an error message

      Outputting an error message would be the most convenient to the user of the console, but doesn't conform with the design of IOStreams. Streams do not output error messages to the console when bad input is detected, so your facet should not either.

    • Throw an exception

      You have the ability to throw an exception, but note that they will not be propagated outside the stream. This is because streams are programmed by default to not throw exceptions. Instead, they set std::ios_base::badbit in the stream whenever an exception is detected. You would have to set the exceptions() mask on the stream before or after performing input to catch exceptions. Another caveat is that only std::ios_base::failure is thrown from streams, so you would only be able to catch that.

    • Set the stream state

      Setting the stream state makes the most sense for the user of your facet, and stays in line with the design of IOStreams. This way, you won't have to drastically change the way you use your stream. Simply check for success of input in the stream state just as you would do naturally with a normal facet.

    Setting the stream state is the approach we will use in the following code:

    #include <locale>
    
    class num_get : public std::num_get<char>
    {
    public:
        // Override do_get which is a virtual function in the std::num_get<char>
        // base class. It is called by the public member function get() in the
        // implementation of std::basic_istream<charT>::operator>>(int&)
    
        // You might want to put this into a helper function and call it in
        // both the signed and unsigned overloads
    
        iter_type do_get( iter_type it, iter_type end, std::ios_base& str,
                          std::ios_base::iostate& err, long& v ) const
        {
            // Store a locale object for later use.
            std::locale loc(str.getloc());
    
            // delegate the extraction to the default base class function
            it = std::num_get<char>::do_get(it, end, str, err, v);
            
            // If the extraction succeeded, tell the user if positive or negative,
            // or zero
            if (!(err & std::ios_base::failbit))
            {
                if (v == 0)
                    std::cout << "The number you entered is a zero.\n";
                std::cout << "You entered a " <<
                    ((v >= 0) ? "positive" : "negative") << " number.\n";
                        
                // Check whether the end has not been met (because of further
                // input that can't be used in a long). And if the first character
                // of that input is a decimal point (or '.' in en_US) then get
                // rid of that input for convenience, and set failbit
    
                if (it != end && *it == std::use_facet<std::numpunct<char>>(loc).decimal_point())
                {
                    // We get rid of that input by calling the base class function
                    // again which does all the necessary parsing.
    
                    // Note that if you do not want to get rid of the invalid
                    // floating point input, then simply remove these two lines.
                    it = std::num_get<char>::do_get(++it, end, str, err, v);
                    // Clear out v
                    v = 0;
                    
                    // set failbit
                    err |= std::ios_base::failbit;
                }
            }    
            return it;
        }
    };
    

    To set up this facet in the stream, you install it into a locale and "imbue" that locale into the stream. Like this:

    // Create a new locale with std::num_get<char> facet replaced by our custom facet
    std::locale new_locale(std::cin.getloc(), new num_get);
    // Imbue this new locale into std::cin
    std::cin.imbue(new_locale);
    

    There's no need to delete the facet once you are done. This is handled by the destructor of the locale that holds it.

    Imbuing the locale should be done before you actually use the stream if you want to get the different behavior.

    Live Example