views:

4009

answers:

5

So I am currently learning C++ and decided to make a program that tests my skills I have learned so far. Now in my code I want to check if the value that the user enters is a double, if it is not a double I will put a if loop and ask them to reenter it. The problem I have is how do I go about checking what type of variable the user enters, ex- if a user enters a char or string, I can output an error message. Here is my code:

//cubes a user entered number
#include <iostream>
using namespace std;

double cube(double n); //function prototype

int main()
{
 cout << "Enter the number you want to cube: "; //ask user to input number
 double user;
 cin >> user;  //user entering the number

 cout << "The cube of " << user << " is " << cube(user) << "." << endl; //displaying the cubed number

 return 0;
}

double cube (double n) //function that cubes the number
{
 return n*n*n; // cubing the number and returning it
}

Edit: I would have to say I just started and don't have the slightest of clue about your code, but I will check out your link. By the way, I haven't learned how to work with templates yet,I am learning about dealing with data, only Chapter 3 in my C++ Primer Plus 5th edition.

+7  A: 

Safe C++ Way

You can define a function for this using std::istringstream:

#include <sstream>  

bool is_double(std::string const& str) {
    std::istringstream ss(str);

    // always keep the scope of variables as close as possible. we see
    // 'd' only within the following block.
    {
        double d;
        ss >> d;
    }

    /* eat up trailing whitespace if there was a double read, and ensure
     * there is no character left. the eof bit is set in the case that
     * `std::ws` tried to read beyond the stream. */
    return (ss && (ss >> std::ws).eof());
}

To assist you in figuring out what it does (some points are simplified):

  • Creation of a input-stringstream initialized with the string given
  • Reading a double value out of it using operator>>. This means skipping whitespace and trying to read a double.
  • If no double could be read, as in abc the stream sets the fail-bit. Note that cases like 3abc will succeed and will not set the fail-bit.
  • If the fail-bit is set, ss evaluates to a zero value, which means false.
  • If an double was read, we skip trailing whitespace. If we then are at the end of the stream (note that eof() will return true if we tried to read past the end. std::ws does exactly that), eof will return true. Note this check makes sure that 3abc will not pass our check.
  • If both cases, right and left of the && evaluate to true, we return true to the caller, signaling the given string is a double.

Similar, you check for int and other types. If you know how to work with templates, you know how to generalize this for other types as well. Incidentally, this is exactly what boost::lexical_cast provides to you. Check it out: http://www.boost.org/doc/libs/1_37_0/libs/conversion/lexical_cast.htm.

C Way One

This way has advantages (being fast) but also major disadvantages (can't generalized using a template, need to work with raw pointers):

#include <cstdlib>
#include <cctype>  

bool is_double(std::string const& s) {
    char * endptr;
    std::strtod(s.c_str(), &endptr);
    if(endptr != s.c_str()) // skip trailing whitespace
        while(std::isspace(*endptr)) endptr++;
    return (endptr != s.c_str() && *endptr == '\0');
}

strtod will set endptr to the last character processed. Which is in our case the terminating null character. If no conversion was performed, endptr is set to the value of the string given to strtod.

C Way Two

One might thing that std::sscanf does the trick. But it's easy to oversee something. Here is the correct way to do it:

#include <cstdio>

bool is_double(std::string const& s) {
    int n;
    double d;
    return (std::sscanf(s.c_str(), "%lf %n", &d, &n) >= 1 && 
            n == static_cast<int>(s.size()));
}

std::sscanf will return the items converted. Although the Standard specifies that %n is not included in that count, serveral sources contradict each other. It's the best to compare >= to get it right (see the manpage of sscanf). n will be set to the amount of the processed characters. It is compared to the size of the string. The space between the two format specifiers accounts for optional trailing whitespace.

Conclusion

If you are a beginner, read into std::stringstream and do it the C++ way. Best not mess with pointers until you feel good with the general concept of C++.

Johannes Schaub - litb
I wouldn't recommend the needless scoping; it just makes the code harder to read, IMO.
strager
it's good style to tell them about it imho :) whether he does it when he's a big playa is another story
Johannes Schaub - litb
Is the space between %lf and %n required or optional? Does it affect the result?
strager
strager. yes. a space means any or zero space. without the space, "3.14 " would assign n=4
Johannes Schaub - litb
A: 

I would have to say I just started and don't have the slightest of clue about your code, but I will check out your link. By the way, I haven't learned how to work with templates yet,I am learning about dealing with data, only Chapter 3 in my C++ Primer Plus 5th edition.

Fatmarik
i put your answer into the question. you should do this with an answer in future.
Johannes Schaub - litb
A: 

You can fall back on C and use strtod

You program reads in a string and then passes it to a function that attempts to convert the string into double.

bool is_double(const char* strIn, double& dblOut) {
    char* lastConvert = NULL;
    double d = strtod(strIn, &lastConvert);
    if(lastConvert == strIn){
        return false;
    } else {
       dblOut = d;
       return true;
    }
}
maccullt
+4  A: 
Federico Ramponi
Don't worry about templates for now. Think of lexical_cast<double>() simply as a function that tries to convert to double and returns a double.
Federico Ramponi
+1  A: 

sscanf can do what you want; it returns the number of arguments properly processed. This should get you started:

//cubes a user entered number
#include <iostream>
#include <cstdio>
using namespace std;

double cube(double n); //function prototype

int main()
{
        cout << "Enter the number you want to cube: "; //ask user to input number
        string user;
        cin >> user;  //user entering the number

        // Convert the number to a double.
        double value;
        if(sscanf(user.c_str(), "%lf", &value) != 1)
        {
                cout << "Bad!  " << user << " isn't a number!" << endl;
                return 1;
        }

        cout << "The cube of " << user << " is " << cube(user) << "." << endl; //displaying the cubed number

        return 0;
}

double cube (double n) //function that cubes the number
{
        return n*n*n; // cubing the number and returning it
}

Other methods posted in other answers have their advantages and disadvantages. This one has issues with trailing characters and isn't "C++"-y.

strager
omg yeah why didnt i think about sscanf. probably better if you don't need the generic feature. In c++ however you include cstdio and use std::sscanf :)
Johannes Schaub - litb
however to check whether the string really contains a double (and not something like "3a"), a bit more work is needed. (%n is a nice weapon for this). the simplest way is to use strtod (and *lastConvert == '\0') imho. the safest is to use stringstream imho .
Johannes Schaub - litb