patterncppModerate
Simple C++ calculator which follows BOMDAS rules
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:
Parentheses Vector:
If in the parentheses vector, the parentheses'
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
A little explanation on operators at this point:
To continue with the above example: \$1+1*90\$
The operator stack will look something like this:
The multiplication will be performed first resulting in 90. The program then decides whether or not to place the result of the m
String:
[1][+][1][*][(][9][*][(][11][-][1][)][)]
0 1 2 3 4 5 6 7 8 9 10 11 12Parentheses Vector:
[( position = 4][( position = 7][) position = 11][) position = 12]
0 1 2 3If 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
operatorClasshas two pointers todoubles 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
Despite being less than one fifth the size, this already checks for balanced parentheses. Right now it's written to take data from
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).
+, -, * 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.