-2

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;
}
TechyGirl98
  • 1
  • 1
  • 4
  • Terminology note: you shoudl probably use "floating point" rather than "decimal" in this case. "decimal" has a particular meaning in many libraries and applications. – Rook May 20 '14 at 18:01
  • Your code already rejects decimal points since you're extracting into an integer, not a floating-point variable. – David G May 20 '14 at 18:02
  • @0x499602D2 it'll read in the bit prior to the point and convert it to an int, but not actually error. – Rook May 20 '14 at 18:03
  • 2
    _'Any good sites with good c++ references other than cplusplus.com'_ Of course: [**cppreference.com**](http://en.cppreference.com/) – πάντα ῥεῖ May 20 '14 at 18:05
  • As for your question: Doesn't `cin >> choice` already guarantee only valid integers can be given? Or is it that you want to prevent the user to input s.th. like `7.8` and it's accepted as valid `int(7)`? Then you may go with Rook's answer. – πάντα ῥεῖ May 20 '14 at 18:09

2 Answers2

0

A fairly easy thing to do is to use something like

std::string line;
std::getline(std::cin, line);

size_t pos;
int x = 0;

try
{
  x = std::stoi(line, &pos);

  if (pos < line.length())
  {
    std::cout << "Warning, non-digit character " << line[pos] << " detected!\n"
    return;
  }
}
catch (std::exception&)
{
  std::cout << "That didn't look like an integer to me.\n";
  return;
}

getline grabs all the text from the input, rather than merely halting at the first character that couldn't be converted into the format you requested (eg. an int). It gets rid of any invconvenient trailing \n for you, too.

std::stoi does the conversion from std::string to int. Read the docs, it can throw exceptions if you're not careful! It returns the position of the first unconverted character in pos. If pos is less then the length of line, it means there's an invalid character in there somewhere that doesn't belong in an int.

Rook
  • 5,734
  • 3
  • 34
  • 43
  • this doesn't take negative numbers into account, and just allowing the '-' character in the list will allow malformed numbers like "5-2". – YoungJohn May 20 '14 at 18:07
  • will `getline` or `find_first_not_of` remove the '\n' character at the end of the string from `cin`? – YoungJohn May 20 '14 at 18:09
  • 2
    `getline` should indeed strip the `\n` – Rook May 20 '14 at 18:11
  • @YoungJohn fixed now, probably. just lets `std::stoi` do the filtering, as I probably should have done in the first place. – Rook May 20 '14 at 18:18
  • `std::stoi` will truncate decimals, instead of disallowing them – Red Alert May 20 '14 at 20:12
  • @RedAlert indeed. And that is why I check `pos`, because `pos < line.length()` when `std::stoi` has stopped early. – Rook May 20 '14 at 20:30
  • @Rook i have never heard of the functions try/catch before, what is it? – TechyGirl98 May 21 '14 at 18:33
  • 1
    @TechyGirl98 there isn't really enough space here to get into detail, but this is called "exception handling". They're not functions, but a control structure, in the same way that `if` isn't a function. Exceptions are one way that C++ handles errors. Its a complex subject, but well worth reading up on! Google will help. – Rook May 21 '14 at 19:33
0

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

Community
  • 1
  • 1
David G
  • 94,763
  • 41
  • 167
  • 253