A lot of programmers are afraid of this kind of thing, and there are a lot of complicated ways to do it.
I am proud that I have done it enough times to have finally arrived at a pretty simple way to implement it. Here is an implementation for your 4 operators, but it extends pretty easily to cover more precedence levels, unary operators, parentheses, and function as well.
The key is the signature of the evaluateNext(int minPrec)
method, which provides an extremely convenient way to divide the problem up into equivalent subproblems:
import java.util.HashMap;
import java.util.regex.*;
public class Calculator {
private static final Pattern TERM_AND_OP_PATTERN = Pattern.compile("([^0-9a-zA-Z]*[0-9a-zA-Z\\.]+\\s*)([-+\\*\\/])");
private static final HashMap<String, Integer> PRECS = new HashMap<>();
{
PRECS.put("+",0);
PRECS.put("-",0);
PRECS.put("*",1);
PRECS.put("/",1);
}
String m_input;
int m_currentPos;
//points at the next operator, or null if none
Matcher m_matcher;
public synchronized double evalutate(String expression) throws Exception
{
m_input = expression;
m_currentPos = 0;
m_matcher = TERM_AND_OP_PATTERN.matcher(m_input);
if (!m_matcher.find())
{
m_matcher = null;
}
try
{
return _evaluateNext(-1);
}
finally
{
m_input = null;
m_matcher = null;
}
}
//Evaluate the next sub-expression, including operators with
//precedence >= minPrec
private double _evaluateNext(int minPrec) throws Exception
{
double val;
if (m_matcher == null)
{
val = Double.parseDouble(m_input.substring(m_currentPos).trim());
}
else
{
val = Double.parseDouble(m_matcher.group(1).trim());
}
while(m_matcher != null)
{
String op = m_matcher.group(2);
//get and check precedence of the next operator
Integer curPrec = PRECS.get(op);
if (curPrec == null)
{
curPrec = 0;
}
if (curPrec<minPrec)
{
//our subexpression ends here
return val;
}
//advance to next term/op
m_currentPos = m_matcher.end();
if (!m_matcher.find())
{
m_matcher = null;
}
switch(op)
{
case "+":
val += _evaluateNext(curPrec+1);
break;
case "-":
val -= _evaluateNext(curPrec+1);
break;
case "*":
val *= _evaluateNext(curPrec+1);
break;
case "/":
val /= _evaluateNext(curPrec+1);
break;
}
}
//found end of the expression
return val;
}
public static void test(String expr) throws Exception
{
System.out.printf("%s = %f\n", expr, new Calculator().evalutate(expr));
}
public static void main(String[] args) throws Exception
{
test("8*2-1*4");
test(" 8 * 2 - 1 * 4 ");
test("-5 + -1 /2");
test("223.3");
test("223.3*2");
}
}