1

I'm writing a program that finds the roots of a quadratic equation using exception handling and i'm wondering if there's a way I can simplify the program, I have a bunch of empty classes for the catch cases

// Program to solve quadratic equation
#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;


class all_zero{
};

class negative{
};

class zero_division{
};

void roots(double a, double b, double c) throw (all_zero,negative,zero_division);

int main(){
    double a, b, c; // coefficient of ax“2 + bx + c= 0
    cout << "Enter the three coefficients \n";
    cin >> a >> b >> c;

    try
    {
        roots(a, b, c);
    }
    catch(all_zero) {cout << "All values are zero \n";}
    catch(negative) {cout << "Square root of negative values is not defined \n";}
    catch(zero_division) {cout << "Division by zero, not defined \n";}
    return 0;
}

void roots(double a, double b, double c) throw (all_zero,negative,zero_division){
    double x1, x2; // The two roots
    double temp;
    if(!(a== 0 && b== 0 && c==0)){
        if(a != 0){
            temp = b*b - 4*a*c;
            if(temp >= 0){
                x1 =(-b + sqrt(temp))/2*a;
                x2 = (-b - sqrt(temp))/2*a;
                cout << "The two roots are: "<< x1 <<" and " << x2 << endl;
            }else{throw negative();}
        }else{throw zero_division();}
    }else{throw all_zero();}
}

Is there a way I can make it so i dont have just empty classes or a way to put them into maybe a struct?

  • 2
    Return a status code? Return a status object. Throw `std::exceptions`. Have `roots` display the output itself. Error callbacks. And many many more options. – Mooing Duck Apr 29 '21 at 17:00
  • 2
    Also consider inverting your If statements to avoid nesting – Mooing Duck Apr 29 '21 at 17:01
  • ... and dropping the forward declaration or `roots` would make it simpler - at least to maintain. – Ted Lyngmo Apr 29 '21 at 17:02
  • This doesn’t address the question, but this code is hard to read. In general, handle error cases at the beginning of the function and normal cases after that. So `if (a ==0 && b == 0 && c == 0) throw all_zero(); if (a == 0) throw zero_division(); double temp = b * b - 4 * a * c; if (temp < 0) throw negative(); …`. – Pete Becker Apr 29 '21 at 17:27
  • Yes; define an `error_category` for your class, and an enumeration of the different errors rather than empty classes. See https://en.cppreference.com/w/cpp/header/system_error – JDługosz Apr 29 '21 at 17:37
  • You may also want to correct the formula. The `/2*a` part should be `/(2*a)` – Ted Lyngmo Apr 29 '21 at 18:32

2 Answers2

3

Note that the dynamic excpetion specification you are using is deprecated in C++11 and removed in C++17:

void roots(double a, double b, double c) throw (all_zero,negative,zero_division)
                                      // ^^ 

You can just remove it.


using namespace std; is considered bad practice.


Do not use std::endl to add a newline. std::endl adds a new line and flushes the stream. Most of the time this is unnecessary. Use '\n' to add a newline.


Is there a way I can make it so i dont have just empty classes or a way to put them into maybe a struct?

Actually, I don't see the bad about having seperate classes for the different types of exceptions. The downside of your approach is that the "what" is in the catch, not part of the exception or coming from where the exception occured. I'll show you the first (message is part of the type) and hope you will see how to realize the latter (message is from where the exception is thrown).

You can inherit from std::runtime_error which offers a what() method that returns a string passed to the constructor. This also makes it easier for others to catch your exceptions, because std::runtime_error in turn inherits from std::excpetion which is the base class of all standard exceptions:

// Program to solve quadratic equation
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <stdexcept>


struct all_zero : std::runtime_error {
    all_zero() : std::runtime_error("All values are zero") {}
};

struct negative : std::runtime_error {
    negative() : std::runtime_error("Square root of negative values is not defined") {}
};

struct zero_division : std::runtime_error {
    zero_division() : std::runtime_error("Division by zero, not defined") {}
};

void roots(double a, double b, double c);

int main(){
    double a, b, c; // coefficient of ax“2 + bx + c= 0
    std::cout << "Enter the three coefficients \n";
    std::cin >> a >> b >> c;

    try
    {
        roots(a, b, c);
    }
    catch(std::runtime_error& ex) {std::cout << ex.what() << '\n';}
    return 0;
}

void roots(double a, double b, double c) {
    double x1, x2; // The two roots
    double temp;
    if(!(a== 0 && b== 0 && c==0)){
        if(a != 0){
            temp = b*b - 4*a*c;
            if(temp >= 0){
                x1 =(-b + sqrt(temp))/2*a;
                x2 = (-b - sqrt(temp))/2*a;
                std::cout << "The two roots are: "<< x1 <<" and " << x2 << "\n";
            }else{throw negative();}
        }else{throw zero_division();}
    }else{throw all_zero();}
}

Live Demo

Note that exceptions should be catched as reference, otherwise there will be object slicing when the exception is derived. And as in general you don't know what type of exception you will catch, you need to be careful to avoid that.

Alternatively you could use a more general exception type (roots_exception?) and instead of hardcoding the "what" pass it to the constructor when you throw it.

There is probably more you can improve, for which I suggest you https://codereview.stackexchange.com/.


PS: I tried to keep style aside, though one more thing you should change is to return the result from the function, instead of just printing it to the screen. If you want to use the result for other calcualtions you currently cannot. Perhaps you were puzzled how to return two values. std::pair makes that simple:

std::pair<double,double> roots(...) {
      // ...
          return {x1,x2};
}

and then:

try {
   auto result = roots(a,b,c);
   std::cout << "The two roots are: "<< result.first <<" and " << result.second << "\n";
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

My input:

  • Make sure the formula is correct. The /2*a part should be /(2*a).
  • Define an error category for your solver's errors and inherit more specific errors from that.
  • Remove unnessary tests (a== 0 && b== 0 && c==0). You already check if a == 0, so if a != 0 but b and c are, the solution will be 0, which should be valid.
  • Reverse the tests to not have to throw in an else. Throw early to not have deep nested if's.
  • Return the result, don't print it. I've added tweaks to make it possible to solve linear and complex equations too (in comments), but you can skip that if you want. This shows how you can return multiple values though.
  • Don't do forward declaration unless you really have to.
  • Read Why is using namespace std; considered bad practice?
  • More details in comments in the code
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <tuple> // std::tuple - used for returning multiple values here

struct solver_error : std::runtime_error { // your solver's error category
    using std::runtime_error::runtime_error;
};

struct unknown_error : solver_error {
    unknown_error() : solver_error("x is unknown") {}
};

struct linear_error : solver_error {
    linear_error() : solver_error("cant solve linear equation") {}
};

struct complex_error : solver_error {
    complex_error() : solver_error("cant solve complex equation") {}
};

// add more exceptions to the solver_error exception category if needed
// now returns two values and a bool to indicate if the solution is complex
std::tuple<double, double, bool> solve_for_x(double a, double b, double c) {
    if(a == 0) throw linear_error();   // throw early

    /* ... or solve it as a linear equation:
    if(a == 0) {
        if(b == 0) throw unknown_error(); // both a and b == 0, x is unknown

        double res = -c / b;
        return {res, res, false}; // only one x and not complex
    }
    */

    double div = 2 * a;
    double temp = b * b - 4 * a * c;

    if(temp < 0) throw complex_error();

    /* ... or solve it as a complex equation
    if(temp < 0) {
        double sqr = sqrt(-temp);           // sqrt of the positive temp instead
        return {-b / div, sqr / div, true}; // real, imaginary and complex==true
    }
    */

    // real quadratic
    double sqr = sqrt(temp);
    double x1 = (-b + sqr) / div; // corrected formula (div = 2*a)
    double x2 = (-b - sqr) / div; // corrected formula

    return {x1, x2, false}; // return the two values and complex==false
}
int main() {
    // make the user aware of what the coefficients are for
    std::cout << "Enter the three coefficients a, b and c in the equation\n"
                 "ax^2 + bx + c = 0\n";

    double a, b, c;

    try {
        // check that extracting the numbers succeeds
        if(not(std::cin >> a >> b >> c)) throw std::runtime_error("input error");

        // get the three return values via structured bindings
        auto [first, second, complex] = solve_for_x(a, b, c);

        std::cout << "x = ";

        if(complex)                                   // complex quadratic
            std::cout << first << " ± " << second << 'i';
        else if(first == second)                      // linear
            std::cout << first;
        else                                          // real quadratic
            std::cout << '{' << first << ',' << second << '}';

        std::cout << '\n';

    } catch(const solver_error& ex) {
        // catching the generic error category by "const&"
        std::cerr << "Solver error: " << ex.what() << '\n';
        return 1;

    } catch(const std::runtime_error& ex) {
        std::cerr << ex.what() << '\n';
        return 1;
    }
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108