Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate a math expression given in string form?

Tags:

java

string

math

I'm trying to write a Java routine to evaluate math expressions from String values like:

  1. "5+3"
  2. "10-40"
  3. "(1+10)*3"

I want to avoid a lot of if-then-else statements. How can I do this?

like image 835
Shah Avatar asked Aug 06 '10 09:08

Shah


People also ask

How do you evaluate mathematical expressions?

To evaluate an algebraic expression, you have to substitute a number for each variable and perform the arithmetic operations. In the example above, the variable x is equal to 6 since 6 + 6 = 12. If we know the value of our variables, we can replace the variables with their values and then evaluate the expression.


2 Answers

With JDK1.6, you can use the built-in Javascript engine.

import javax.script.ScriptEngineManager; import javax.script.ScriptEngine; import javax.script.ScriptException;  public class Test {   public static void main(String[] args) throws ScriptException {     ScriptEngineManager mgr = new ScriptEngineManager();     ScriptEngine engine = mgr.getEngineByName("JavaScript");     String foo = "40+2";     System.out.println(engine.eval(foo));     }  } 
like image 129
RealHowTo Avatar answered Sep 17 '22 14:09

RealHowTo


I've written this eval method for arithmetic expressions to answer this question. It does addition, subtraction, multiplication, division, exponentiation (using the ^ symbol), and a few basic functions like sqrt. It supports grouping using (...), and it gets the operator precedence and associativity rules correct.

public static double eval(final String str) {     return new Object() {         int pos = -1, ch;                  void nextChar() {             ch = (++pos < str.length()) ? str.charAt(pos) : -1;         }                  boolean eat(int charToEat) {             while (ch == ' ') nextChar();             if (ch == charToEat) {                 nextChar();                 return true;             }             return false;         }                  double parse() {             nextChar();             double x = parseExpression();             if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);             return x;         }                  // Grammar:         // expression = term | expression `+` term | expression `-` term         // term = factor | term `*` factor | term `/` factor         // factor = `+` factor | `-` factor | `(` expression `)` | number         //        | functionName `(` expression `)` | functionName factor         //        | factor `^` factor                  double parseExpression() {             double x = parseTerm();             for (;;) {                 if      (eat('+')) x += parseTerm(); // addition                 else if (eat('-')) x -= parseTerm(); // subtraction                 else return x;             }         }                  double parseTerm() {             double x = parseFactor();             for (;;) {                 if      (eat('*')) x *= parseFactor(); // multiplication                 else if (eat('/')) x /= parseFactor(); // division                 else return x;             }         }                  double parseFactor() {             if (eat('+')) return +parseFactor(); // unary plus             if (eat('-')) return -parseFactor(); // unary minus                          double x;             int startPos = this.pos;             if (eat('(')) { // parentheses                 x = parseExpression();                 if (!eat(')')) throw new RuntimeException("Missing ')'");             } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers                 while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();                 x = Double.parseDouble(str.substring(startPos, this.pos));             } else if (ch >= 'a' && ch <= 'z') { // functions                 while (ch >= 'a' && ch <= 'z') nextChar();                 String func = str.substring(startPos, this.pos);                 if (eat('(')) {                     x = parseExpression();                     if (!eat(')')) throw new RuntimeException("Missing ')' after argument to " + func);                 } else {                     x = parseFactor();                 }                 if (func.equals("sqrt")) x = Math.sqrt(x);                 else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));                 else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));                 else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));                 else throw new RuntimeException("Unknown function: " + func);             } else {                 throw new RuntimeException("Unexpected: " + (char)ch);             }                          if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation                          return x;         }     }.parse(); } 

Example:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2")); 

Output: 7.5 (which is correct)


The parser is a recursive descent parser, so internally uses separate parse methods for each level of operator precedence in its grammar. I deliberately kept it short, but here are some ideas you might want to expand it with:

  • Variables:

    The bit of the parser that reads the names for functions can easily be changed to handle custom variables too, by looking up names in a variable table passed to the eval method, such as a Map<String,Double> variables.

  • Separate compilation and evaluation:

    What if, having added support for variables, you wanted to evaluate the same expression millions of times with changed variables, without parsing it every time? It's possible. First define an interface to use to evaluate the precompiled expression:

      @FunctionalInterface   interface Expression {       double eval();   } 

    Now to rework the original "eval" function into a "parse" function, change all the methods that return doubles, so instead they return an instance of that interface. Java 8's lambda syntax works well for this. Example of one of the changed methods:

      Expression parseExpression() {       Expression x = parseTerm();       for (;;) {           if (eat('+')) { // addition               Expression a = x, b = parseTerm();               x = (() -> a.eval() + b.eval());           } else if (eat('-')) { // subtraction               Expression a = x, b = parseTerm();               x = (() -> a.eval() - b.eval());           } else {               return x;           }       }   } 

    That builds a recursive tree of Expression objects representing the compiled expression (an abstract syntax tree). Then you can compile it once and evaluate it repeatedly with different values:

      public static void main(String[] args) {       Map<String,Double> variables = new HashMap<>();       Expression exp = parse("x^2 - x + 2", variables);       for (double x = -20; x <= +20; x++) {           variables.put("x", x);           System.out.println(x + " => " + exp.eval());       }   } 
  • Different datatypes:

    Instead of double, you could change the evaluator to use something more powerful like BigDecimal, or a class that implements complex numbers, or rational numbers (fractions). You could even use Object, allowing some mix of datatypes in expressions, just like a real programming language. :)


All code in this answer released to the public domain. Have fun!

like image 29
Boann Avatar answered Sep 20 '22 14:09

Boann