andy0130tw
12/30/2015 - 1:02 PM

fraction calculator

fraction calculator

//================================================================
//  PROGRAMMER  : 潘廣霖
//  DATE        : 2015-12-30
//  FILENAME    : HW07B002.CPP
//  DESCRIPTION : A naive, OOP fraction calculator
//================================================================

#include<iostream>
#include<iomanip>
#include<sstream>
#include<cstdlib>
#include<cmath>

using namespace std;

// you can switch to long long here easily
typedef int INTEGER;

INTEGER gcd(INTEGER a, INTEGER b) {
    return a ? gcd(b % a, a) : abs(b);
}

class Fractions {
public:
    Fractions() { num = 0; denom = 1; }
    Fractions(INTEGER n, INTEGER d) {
        if (d == 0) throw false;
        num = n;
        denom = d;
        normalize();
    }
    friend ostream& operator << (ostream&, const Fractions&);
    
    Fractions operator + (Fractions rhs) {
        INTEGER d = gcd(denom, rhs.denom);
        return Fractions(rhs.denom / d * num + denom / d * rhs.num, denom / d * rhs.denom);
    }
    Fractions operator - (Fractions rhs) {
        INTEGER d = gcd(denom, rhs.denom);
        return Fractions(rhs.denom / d * num - denom / d * rhs.num, denom / d * rhs.denom);
    }
    Fractions operator * (Fractions rhs) {
        // TODO: avoid overflow
        return Fractions(num * rhs.num, denom * rhs.denom);
    }
    Fractions operator / (Fractions rhs) {
        // TODO: avoid overflow
        INTEGER n = denom * rhs.num;
        if (n == 0) throw false;
        return Fractions(num * rhs.denom, n);
    }
private:
    int num;
    int denom;
   
    void normalize() {
        int g = gcd(num, denom);
        num /= g;
        denom /= g;
        // take negative sign to the numerator
        if (denom < 0) {
            num *= -1;
            denom *= -1;
        }
    }
};

ostream& operator << (ostream& os, const Fractions& f) {
    if (f.denom == 1) {
        // avoid fractions like (2/1), they are integers
        os << f.num;
        return os;
    }
    os << "(" << f.num << "/" << f.denom << ")";
    return os;
}

inline bool isValidOp(char op) {
    return (op == '+' || op == '-' || op == '*' || op == '/');
}

void printHeader() {
    cout << "========= FRACTION CALCULATOR =========" << endl;
    cout << "Syntax: <fraction> [<op> <fraction>]" << endl;
    cout << "Input '?' for more details." << endl;
    cout << "=======================================" << endl;
    cout << endl;
}

void printUsage() {
    cout << "=============================================" << endl;
    cout << "Grammar:" << endl;
    cout << " <fraction>: <INT> ['/' <INT>]" << endl;
    cout << "       <op>: '+' | '-' | '*' | '/'" << endl;
    cout << "Examples:" << endl;
    cout << "  \"1/3+2/5\": (1/3) + (2/5) => (11/15)" << endl;
    cout << "  \"-3/4-4\": (-3/4) - (4/1) => (-19/4)" << endl;
    cout << "  \"6/2/2/1\": (6/2) / (2/1) => (3/2)" << endl;
    cout << "  \"7/0\": error, divided by zero!!" << endl;
    cout << "  \"7/2/0/1\": (7/2) / (0/1) => error!!" << endl;  
    cout << "Notes:" << endl;  
    cout << "  * Never divide with zero. Never." << endl;
    cout << "  * The input format is different from common" << endl;
    cout << "    expressions in real life." << endl;
    cout << "=============================================" << endl;
    cout << endl;
}

bool parseError(stringstream& ss, string errMessage, int offset = 0) {
    ss.clear();
    int colNo = (int) ss.tellg();
    if (colNo <= 0) colNo = 1;
    colNo += offset;
    cerr 
    //   << ss.str() << endl
         << setw(colNo) << right << "^" << endl
         << "Parse error: col #" << colNo << ", " << errMessage << endl;
    return false;
}

bool inputOperator(stringstream& s, char& op) {
    s >> op;
    if (s.fail()) {
        op = '\0';
        return true;
    }
    if (!isValidOp(op))
        return parseError(s, "invalid operator!!");
    return true;
}

bool inputFraction(stringstream& s, INTEGER& x, INTEGER& y) {
    s >> x;
    if (s.fail() && s.eof())
        return parseError(s, "numerator expected!!");        
    if (s.fail())
        return parseError(s, "invalid numerator!!");
    
    char c;
    s >> c;
    if (c != '/') {
        y = 1;
        s.unget();
        return true;
        //return parseError(s, "\"/\" expected for a fraction!!");
    }
    
    s >> y;
    if (s.fail() && s.eof()) 
        return parseError(s, "denominator expected!!", 1);
    if (s.fail())
        return parseError(s, "invalid denominator!!", 1);
        
    if (y == 0)
        return parseError(s, "zero cannot be a denominator!!");
    
    return true;
}

bool parseCommand(string l, Fractions*& fr1, Fractions*& fr2, char& op) {
    INTEGER a, b;
    stringstream ss(l);
    
    if (!inputFraction(ss, a, b)) return false;
    fr1 = new Fractions(a, b);
    
    if (!inputOperator(ss, op)) return false;
    if (!op) return true;
    
    if (!inputFraction(ss, a, b)) return false;
    fr2 = new Fractions(a, b);
    
    // ensure no extra character
    char tmp;
    ss >> tmp;
    if (!ss.fail()) return parseError(ss, "extra character!!");
    return true;
}

int main() {
    char op;
    
    Fractions* fr1 = 0;
    Fractions* fr2 = 0;
    Fractions ans;
    
    printHeader();
    string l;
    while (getline(cin, l)) {
    
        if (l == "?") {
            printUsage();
            continue;
        }
    
        if (!parseCommand(l, fr1, fr2, op)) {
            cout << endl;
            continue;
        }
        
        if (op == '\0') {
            cout << "Input:  " << *fr1 << endl;
        } else {
            if (op == '+')
                ans = *fr1 + *fr2;
            else if (op == '-')
                ans = *fr1 - *fr2;
            else if (op == '*')
                ans = *fr1 * *fr2;
            else if (op == '/')
                try {
                    ans = *fr1 / *fr2;
                } catch (bool b) {
                    cout << "Exception: cannot divided by zero!!" << endl;
                    cout << endl;
                    continue;
                }
    
            cout << "Input:  " << *fr1 << " " << op << " " << *fr2 << endl;
            cout << "Result: " << ans << endl;
        }
        
        // empty line for each line of input
        cout << endl;
        
        // recycle
        delete fr1; fr1 = 0;
        delete fr2; fr2 = 0;
    }
}