As a coding exercise, I am trying to implement a calculator which takes string as input, with addition (+), subtraction (-), multiplication (*), division (/) and power (^) functions. For example, given input string "-12.3456 + 2 ^ 3 * 3.123" to a function, it can output double result "12.6384".
The experimental implementation
StringCalculator
class implementationclass StringCalculator { public: StringCalculator(const std::string& input) { // remove spaces auto input_without_space = remove_spaces(input); parser(input_without_space); while(operators.size() > 0) { perform_computing(); } } long double get_result() { return numbers[0]; } private: std::vector<long double> numbers; std::vector<std::string> operators; constexpr void parser(std::string_view input_string) { int floating_point_count = 0; long double single_number = 0; bool floating_point = false; for(std::size_t index = 0; index < input_string.size(); ++index) { if(std::isdigit(input_string[index]) || input_string[index] == '.') { if(input_string[index] != '.') { if(floating_point == false) { single_number = single_number * 10 + (static_cast<int>(input_string[index]) - 48); } else { floating_point_count = floating_point_count + 1; single_number = single_number + (static_cast<int>(input_string[index]) - 48) / std::pow(10, floating_point_count); } } else { if(floating_point) throw std::runtime_error("Float-point parsing error!"); else floating_point = true; } } else { if( input_string[index] == '+' || input_string[index] == '-' || input_string[index] == '*' || input_string[index] == '/' || input_string[index] == '^') { operators.push_back(std::string{input_string[index]}); numbers.push_back(single_number); single_number = 0; floating_point = false; floating_point_count = 0; } } } numbers.push_back(single_number); } constexpr void perform_computing() { // level 1 computation: ^ (power) for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "^") { numbers[index] = std::pow(numbers[index], numbers[index + 1]); reduce_operators_and_numbers(index); } } // level 2 computation: * (multiplication) and / (division) for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "*") { numbers[index] = numbers[index] * numbers[index + 1]; reduce_operators_and_numbers(index); } if(operators[index] == "/") { numbers[index] = numbers[index] / numbers[index + 1]; reduce_operators_and_numbers(index); } } // level 3 computation: + (addition) and - (subtraction) for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "+") { numbers[index] = numbers[index] + numbers[index + 1]; reduce_operators_and_numbers(index); } if(operators[index] == "-") { numbers[index] = numbers[index] - numbers[index + 1]; reduce_operators_and_numbers(index); } } } constexpr void reduce_operators_and_numbers(std::size_t index) { for(std::size_t index1 = index; index1 < operators.size() - 1; ++index1) { operators[index1] = operators[index1 + 1]; } operators.resize(operators.size() - 1); for(std::size_t index1 = index; index1 < numbers.size() - 2; ++index1) { numbers[index1 + 1] = numbers[index1 + 2]; } numbers.resize(numbers.size() - 1); } // copy from https://stackoverflow.com/a/83481/6667035 std::string remove_spaces(std::string str) { std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' '); str.erase(end_pos, str.end()); return str; } };
Full Testing Code
The full testing code:
// String Calculator in C++
#include <algorithm>
#include <cassert>
#include <cctype>
#include <chrono>
#include <cmath>
#include <concepts>
#include <execution>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <ranges>
#include <stack>
#include <string>
// From https://stackoverflow.com/a/37264642/6667035
#ifndef NDEBUG
# define M_Assert(Expr, Msg) \
M_Assert_Helper(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
# define M_Assert(Expr, Msg) ;
#endif
void M_Assert_Helper(const char* expr_str, bool expr, const char* file, int line, const char* msg)
{
if (!expr)
{
std::cerr << "Assert failed:\t" << msg << "\n"
<< "Expected:\t" << expr_str << "\n"
<< "Source:\t\t" << file << ", line " << line << "\n";
abort();
}
}
class StringCalculator
{
public:
StringCalculator(const std::string& input)
{
// remove spaces
auto input_without_space = remove_spaces(input);
parser(input_without_space);
while(operators.size() > 0)
{
perform_computing();
}
}
long double get_result()
{
return numbers[0];
}
private:
std::vector<long double> numbers;
std::vector<std::string> operators;
constexpr void parser(std::string_view input_string)
{
int floating_point_count = 0;
long double single_number = 0;
bool floating_point = false;
for(std::size_t index = 0; index < input_string.size(); ++index)
{
if(std::isdigit(input_string[index]) || input_string[index] == '.')
{
if(input_string[index] != '.')
{
if(floating_point == false)
{
single_number = single_number * 10 + (static_cast<int>(input_string[index]) - 48);
}
else
{
floating_point_count = floating_point_count + 1;
single_number = single_number + (static_cast<int>(input_string[index]) - 48) / std::pow(10, floating_point_count);
}
}
else
{
if(floating_point)
throw std::runtime_error("Float-point parsing error!");
else
floating_point = true;
}
}
else
{
if( input_string[index] == '+' ||
input_string[index] == '-' ||
input_string[index] == '*' ||
input_string[index] == '/' ||
input_string[index] == '^')
{
operators.push_back(std::string{input_string[index]});
numbers.push_back(single_number);
single_number = 0;
floating_point = false;
floating_point_count = 0;
}
}
}
numbers.push_back(single_number);
}
constexpr void perform_computing()
{
// level 1 computation: ^ (power)
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "^")
{
numbers[index] = std::pow(numbers[index], numbers[index + 1]);
reduce_operators_and_numbers(index);
}
}
// level 2 computation: * (multiplication) and / (division)
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "*")
{
numbers[index] = numbers[index] * numbers[index + 1];
reduce_operators_and_numbers(index);
}
if(operators[index] == "/")
{
numbers[index] = numbers[index] / numbers[index + 1];
reduce_operators_and_numbers(index);
}
}
// level 3 computation: + (addition) and - (subtraction)
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "+")
{
numbers[index] = numbers[index] + numbers[index + 1];
reduce_operators_and_numbers(index);
}
if(operators[index] == "-")
{
numbers[index] = numbers[index] - numbers[index + 1];
reduce_operators_and_numbers(index);
}
}
}
constexpr void reduce_operators_and_numbers(std::size_t index)
{
for(std::size_t index1 = index; index1 < operators.size() - 1; ++index1)
{
operators[index1] = operators[index1 + 1];
}
operators.resize(operators.size() - 1);
for(std::size_t index1 = index; index1 < numbers.size() - 2; ++index1)
{
numbers[index1 + 1] = numbers[index1 + 2];
}
numbers.resize(numbers.size() - 1);
}
// copy from https://stackoverflow.com/a/83481/6667035
std::string remove_spaces(std::string str)
{
std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' ');
str.erase(end_pos, str.end());
return str;
}
};
// Copy from: https://stackoverflow.com/a/37686/6667035
constexpr bool AreSame(double a, double b) {
return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}
int main()
{
auto start = std::chrono::system_clock::now();
StringCalculator sc1("-12.3456 + 2 ^ 3 * 3.123");
M_Assert(
AreSame(sc1.get_result(), 12.6384),
"\"-12.3456 + 2 ^ 3 * 3.123\" test case failed");
StringCalculator sc2("-0.12345 * 3 - 5");
M_Assert(
AreSame(sc2.get_result(), -5.37035),
"\"-0.12345 * 3 - 5\" test case failed");
StringCalculator sc3("1024*2+2048*4");
M_Assert(
AreSame(sc3.get_result(), 10240),
"\"1024*2+2048*4\" test case failed");
StringCalculator sc4("10-2*3+2*2-7");
M_Assert(
AreSame(sc4.get_result(), 1),
"\"10-2*3+2*2-7\" test case failed");
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
return 0;
}
The output of the test code above:
Computation finished at Sat May 4 12:48:52 2024
elapsed time: 1.0409e-05
All suggestions are welcome.
.
as a decimal separator might be not the most accessible - some people around the globe really use commas for decimal and dots - for thousands. You can get the default separator from locale. \$\endgroup\$