HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Simple C++ calculator which follows BOMDAS rules

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplefollowsbomdaswhichrulescalculator

Problem

This was my first attempt at making a C++ calculator. The user basically inputs an expression (I'll use \$1+1(9(11-1))\$ as an example) and the program searches for and creates a vector for parentheses (which is a class I made which records its position in the string).

String:

[1][+][1][*][(][9][*][(][11][-][1][)][)]
 0  1  2  3  4  5  6  7  8   9 10 11 12


Parentheses Vector:

[( position = 4][( position = 7][) position = 11][) position = 12]
       0                 1               2               3


If in the parentheses vector, the parentheses' position = n, then the corresponding closing parentheses will be vector.size()-n.

The program deals with the first parenthesis and finds its corresponding closing parenthesis using the above statement and what I call the sub expression is found between the position member variable of the selected parentheses. For the innermost set of parentheses, the sub expression will be thus: 11-1

The expression will be evaluated (10) and the result will be placed in the expression:

\$1+1(910)\$

and the same for the other sub expression:

\$1+1*90\$

In terms of operator precedence, the B in BOMDAS has been followed so far, so the other operators are to be used (*,/,+,- are all supported at the moment).

Each operator has an assigned value in a map called operatorClassMap:

operatorClassMap['*']=2;
operatorClassMap['/']=2;
operatorClassMap['+']=1;
operatorClassMap['-']=1;


A little explanation on operators at this point:

  • The class operatorClass has two pointers to doubles in the expression.



  • The class records the operator's "value" assigned above.



  • I overloaded the `



  • The vector is transferred to a stack and evaluated from the top.



To continue with the above example: \$1+1*90\$

The operator stack will look something like this:

[ 1 * 90]
[ 1 + 1]


The multiplication will be performed first resulting in 90. The program then decides whether or not to place the result of the m

Solution

If I were going to carry out this general task, I'd probably use a recursive descent parser. A simple version to handle +, -, * and / with the correct precedence can look something like this:

#include 
#include 
#include 

int expression();

char token() { 
    char ch;
    std::cin >> ch;
    return ch;
}

int factor() { 
    int val = 0;
    char ch = token();
    if (ch == '(') {
        val = expression();
        ch = token();
        if (ch != ')') {
            std::string error = std::string("Expected ')', got: ") + ch;
            throw std::runtime_error(error.c_str());
        }
    }
    else if (isdigit(ch)) {
        std::cin.unget();
        std::cin >> val;
    }
    else throw std::runtime_error("Unexpected character");
    return val;
}

int term() { 
    int ch;
    int val = factor();
    ch = token();
    if (ch == '*' || ch == '/') {
        int b = term();
        if (ch == '*')
            val *= b;
        else
            val /= b;
    }
    else std::cin.unget();
    return val;
}

int expression() {
    int val = term();
    char ch = token();
    if (ch == '-' || ch=='+') {
        int b = expression();
        if (ch == '+')
            val += b;
        else
            val -= b;
    }
    else std::cin.unget();
    return val;
}

int main(int argc, char **argv) {
    try {
        std::cout << expression();
    }
    catch(std::exception &e) {
        std::cout << e.what();
    }
    return 0;
}


Despite being less than one fifth the size, this already checks for balanced parentheses. Right now it's written to take data from std::cin, but getting it to read from an arbitrary stream (including a stringstream) would be trivial. Getting it to parse input from a string would be minutely more work (the obvious way would be to create a stringstream and read from there, but you could just keep track of the current position to read from in a string as well).

The shunting-yard algorithm is also well known for this task. I don't see a huge advantage to it myself, but some (many?) people find it somewhat simpler. Depending on how fast function calls are on your particular CPU, it may execute faster as well (but most modern CPUs make function calls pretty fast so if there's an improvement in speed, it probably won't be very large).

Code Snippets

#include <iostream>
#include <string>
#include <cctype>

int expression();

char token() { 
    char ch;
    std::cin >> ch;
    return ch;
}

int factor() { 
    int val = 0;
    char ch = token();
    if (ch == '(') {
        val = expression();
        ch = token();
        if (ch != ')') {
            std::string error = std::string("Expected ')', got: ") + ch;
            throw std::runtime_error(error.c_str());
        }
    }
    else if (isdigit(ch)) {
        std::cin.unget();
        std::cin >> val;
    }
    else throw std::runtime_error("Unexpected character");
    return val;
}

int term() { 
    int ch;
    int val = factor();
    ch = token();
    if (ch == '*' || ch == '/') {
        int b = term();
        if (ch == '*')
            val *= b;
        else
            val /= b;
    }
    else std::cin.unget();
    return val;
}

int expression() {
    int val = term();
    char ch = token();
    if (ch == '-' || ch=='+') {
        int b = expression();
        if (ch == '+')
            val += b;
        else
            val -= b;
    }
    else std::cin.unget();
    return val;
}

int main(int argc, char **argv) {
    try {
        std::cout << expression();
    }
    catch(std::exception &e) {
        std::cout << e.what();
    }
    return 0;
}

Context

StackExchange Code Review Q#54273, answer score: 11

Revisions (0)

No revisions yet.