-1

I'm trying to write a script where the user will be inputting a radius and then the console will display the Volume and Surface Area of a sphere. If the input radius is negative, the user will be prompted to enter a positive radius until the condition is met. I've managed to do this but without validating the positive radius bit. How can I achieve this?

My code:

/*
 * Calculate the volume and surface area of a sphere.
 *
 */

#include <iostream>
#include <string>
#include <sstream>
#include <cmath> // Include cmath for M_PI constant
using namespace std;

int main()
{
    const double pi = M_PI; /// Value of PI defined by C++
    string input = "";      /// Temporary input buffer
    double r = 0.0;         /// Sphere radius
    double A = 0.0;         /// Sphere area
    double V = 0.0;         /// Sphere volume

    // Request radius
    cout << "Please enter radius of sphere (positive only): ";

    // Get string input from user (up to next press of <enter> key)
    getline(cin, input);

    // Try to convert input to a double
    r = stod(input);

    // making sure r is positive
    if (r > 0)
    {
        // Calculate area and volume
        // Ensure floating-point division instead of integer division by
        // explicitly writing 4.0/3.0
        A = 4.0 * pi * r * r;
        V = (4.0 / 3.0) * pi * r * r * r;

        // Write out result
        cout << "Sphere radius: " << r << endl;
        cout << "Sphere area:   " << A << endl;
        cout << "Sphere volume: " << V << endl;
    }
    else
    {
        while (r < 0)
        {
            cout << "Please enter radius of sphere (positive only): " << endl;
        }
    }

    // Return success
    return 0;
}
SpeedBird
  • 53
  • 6
  • 1
    `stod()` will crash your program if a non-double was entered. For example "foo". Your comment tells you what you need to do. `stod()` needs to be within a `try` block. – sweenish Dec 05 '22 at 14:48
  • You need to call `getline(cin, input);` and `r = stod(input);` inside the while loop, but then if you get a positive number you need to jump back to the ok part. Make an inout function and do the `while` in there? – doctorlove Dec 05 '22 at 14:48
  • Also, as of C++20, [``](https://en.cppreference.com/w/cpp/numeric/constants) defines `std::numbers::pi`. – sweenish Dec 05 '22 at 14:51
  • Finally, my recommendation would be to take care of getting your data and ensuring it's all correct, **before** doing any calculations. You partially validate your input, do calculations, then check your input again. Talk these things out before writing code. – sweenish Dec 05 '22 at 14:55

3 Answers3

1

First, this code is not awful. Compared to what I've seen from some other beginners, this code demonstrates that there is a decent understanding of fundamentals up to this point.

The biggest issue facing your code is the order of operations. If you want input from the user, you need to validate it before processing it. Currently, you're doing a bit of both at the same time. As mentioned, create a loop that does not exit until you have valid inputs. Then go ahead and do your math. This is separating your concerns and is a best practice.

Other nitpicks include using namespace std; as a bad practice, and one you should get out of doing sooner than later. Front-declaring your variables is also bad practice. Declare at or near first use. std::string input; suffices for a default string, there is no need to = "";.

And as I commented, stod() can throw an exception and abort your program if the input cannot be converted. You don't mention whether you're allowed to assume your input will always be a number or not so I can't assume it is.

/*
 * Calculate the volume and surface area of a sphere.
 *
 */

#include <cmath>
#include <iostream>
#include <numbers>
#include <string>

int main() {
  double radius = 0.0;
  bool inputIsInvalid = true;

  do {
    std::string input;
    std::cout << "Enter a radius: ";
    std::getline(std::cin, input);

    std::size_t pos = 0;
    try {
      radius = std::stod(input, &pos);
    } catch (const std::exception& e) {
      std::cerr << "Unable to convert to double. Reason: " << e.what() << '\n';
      continue;
    }

    // We're still not done checking. We need to ensure that the entire string
    // was converted. If not, the input was invalid.
    if (pos != input.length()) {
      std::cerr << "Invalid characters added. Try again.\n";
      continue;
    }

    // Making it here means a valid double was typed in.
    // Now we ensure that the double is positive.
    if (radius < 0.0) {
      std::cerr << "Please enter a positive number. Try again.\n";
      continue;
    }

    // Making it here should mean that we have a valid input.
    inputIsInvalid = false;

  } while (inputIsInvalid);

  // Now we can do math!
  using namespace std::numbers;  // C++20 stuff for pi
  double surfaceArea = 4.0 * pi * std::pow(radius, 2);
  double volume = (4.0 / 3.0) * pi * std::pow(radius, 3);

  std::cout << "For a sphere of radius: " << radius << '\n'
            << "Surface area: " << surfaceArea << '\n'
            << "Volume: " << volume << '\n';
}

Output:

❯ ./a.out 
Enter a radius: foo
Unable to convert to double. Reason: stod: no conversion
Enter a radius: 3o
Invalid characters added. Try again.
Enter a radius: -3
Please enter a positive number. Try again.
Enter a radius: 3
For a sphere of radius: 3
Surface area: 113.097
Volume: 113.097

As you can see, all of the getting of input and validation occurs within the big do/while loop. If we are out of the loop, we know that we have a valid value, and doing the math is now very straightforward.

sweenish
  • 4,793
  • 3
  • 12
  • 23
1

There is no need for complicated statements.

You just need to understand that IO operation notice, when there was a problem. And then they set failure bits, which you can check.

Please look in the CPP reference at this link. There is a long description of what could happen and what failure bit will be set and how it can be tested.

So, if you use for example the stand extraction operator >> like in std::cin >> radius then this operator will try to read a value from the console and convert it to a double. If it cannot do that, because you entered for example "abc" instead of a number, a failure bit will be set. The std::cinis then in state fail and does not work any longer.

If you want to continue to use std::cin then you must clear the fail bits with the clear() function. So, you need to write std::cin.clear();.

But this is not sufficient. There might be still some other characters in the input buffer. And those need to be removed. Imagine that you enter "XYZ" instead of a number, then std::cin will go into failure state after reading the first character 'X'. We need to eliminate all the wrong characters, because otherwise, they will be read again with the next >> operation.

For this we have the function ignore. Please read here about the function and look at the example at the bottom of the page. Exactly what you need.

Next: How can we check for an error of an IO operation?

You may have heard that we can chain IO operations. For example: int a,b; std::cin >> a >> b; or, for the output case std::cout << value << "\n";

Why does this work? You need to understand the the extraction and inserter operator >> and << return a reference to the stream for which they have been called.

So, std::cin >> a; will return a reference to std::cin. And that is the reason why you can chain IO operations.

std::cin >> a >> b; will first do std::cin >> a which will return std::cin. The rest of the expression will now be std::cin >> b;. also this will be performed and again std::cin will be returned.

And this we can use. The basic:ios has 2 operators to check the failure state.

  1. the bool operator
  2. the not operator !

So, you can check the state of std::cin simply with if (std::cin). And because the if-statement expects a bool expression, it will call the streams bool operator. And with that get the state.

And now, what we learned above: if (std::cin >> a) will try to read a value into "a" then return std::cin and then its bool operator is called.

Now we found a possibility to check for a correct IO operation with if (std::cin >> radius) But of course we can do more test in the if statement. You have often seen and && or or|| operators in conditions. You can make use of it. And especially you can make use of boolean shortcut evaluation.

Meaning, if the outcome of a condition is already clear by evaluation the first term, then the second term will not be evaluated.

So, we can write if ((std::cin >> radius) and (radius > 0.0)) to check for a valid input. If the reading of the input fails, then the check for greater than 0 will not be executed. It will only be executed, if the input was successful.

With all the above, we can now draft the below very simple solution:

#include <iostream>
#include <limits>

int main() {
    double radius = 0.0;
    bool valueOK = false;

    while (not valueOK) {
        std::cout << "\n\nInsert radius. A positive value: ";
        if ((std::cin >> radius) and (radius > 0.0))
            valueOK = true;
        else {
            std::cout << "\n\n***Error: invalid input\n";
        }
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    const double PI = 3.14159265358979323846;
    std::cout << "\n\nSphere Radius:\t" << radius
        << "\nSphere area:\t" << 4.0 * PI * radius * radius
        << "\nSphere volume:\t" << 4.0 / 3.0 * PI * radius * radius * radius << '\n';
}

No need for complicated statements.

A M
  • 14,694
  • 5
  • 19
  • 44
0

You could use std::from_chars within an infinite loop to read a radius of type double (see here):

  • It receives a couple of const char* to the beginning and end of the string you want to parse, and a reference to a double value (that will be set if the parsing went OK).
  • It returns a pointer pointing to the first character that didn't match the pattern for a double value, and an error.
  • If the error is "empty" (just value-initialized), and the pointer points to the end of the input string, it means that all the characters were used to parse the double value (i.e. there were no extra characters at the beginning or at the end in the input string).

[Demo]

#include <charconv>  // from_chars
#include <iostream>  // cin, cout
#include <numbers>  // Include cmath for M_PI constant
#include <sstream>
#include <string>  // getline
#include <system_error>  // errc

int main() { 
    // Request radius
    double r = 0.0;  /// Sphere radius
    for (;;) {
        std::cout << "Please enter radius of sphere (positive only): ";

        // Get string input from user (up to next press of <enter> key)
        std::string input = "";  /// Temporary input buffer
        getline(std::cin, input);
        std::cout << input << "\n";

        // Try to convert input to a double
        auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), r);
        if (ec != std::errc{} or ptr != input.data() + input.size() or r < 0) {
            std::cout << "Invalid input: '" << input << "'\n";
        } else {
            break;
        }
    }

    // Calculate area and volume
    // Ensure floating-point division instead of integer division by
    // explicitly writing 4.0/3.0
    double A = 4.0 * std::numbers::pi_v<double> * r * r;  /// Sphere area
    double V = (4.0 / 3.0) * std::numbers::pi_v<double> * r * r * r;  /// Sphere volume

    // Write out result
    std::cout << "Sphere radius: " << r << "\n";
    std::cout << "Sphere area:   " << A << "\n";
    std::cout << "Sphere volume: " << V << "\n";
 
    // Return success
    return 0;
}

// Outputs:
//
//   Please enter radius of sphere (positive only): -5
//   Invalid input: '-5'
//   Please enter radius of sphere (positive only): hola
//   Invalid input: 'hola'
//   Please enter radius of sphere (positive only): 25abc
//   Invalid input: '25abc'
//   Please enter radius of sphere (positive only): 1.5
//   Sphere radius: 1.5
//   Sphere area:   28.2743
//   Sphere volume: 14.1372
rturrado
  • 7,699
  • 6
  • 42
  • 62