如何评估以字符串形式给出的数学表达式?

Sha*_*hah 303 java string math

我正在尝试编写一个Java例程来评估简单的数学表达式,String例如:

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

我想避免很多if-then-else语句.我怎样才能做到这一点?

Rea*_*wTo 361

使用JDK1.6,您可以使用内置的Javascript引擎.

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));
    } 
}
Run Code Online (Sandbox Code Playgroud)

  • 似乎那里存在一个主要问题; 它执行脚本,而不是计算表达式.要清楚,engine.eval("8; 40 + 2"),输出42!如果你想要一个也检查语法的表达式解析器,我刚刚完成了一个(因为我找不到任何适合我需要的东西):[Javaluator](http://javaluator.sourceforge.net). (48认同)
  • 安全说明:您不应该在具有用户输入的服务器上下文中使用它.执行的JavaScript可以访问所有Java类,从而无限制地劫持您的应用程序. (30认同)
  • @partho`new javax.script.ScriptEngineManager().getEngineByName("JavaScript").eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");` - 将通过JavaScript将文件写入(默认情况下)程序的当前目录 (14认同)
  • 作为旁注,如果需要在代码中的其他地方使用此表达式的结果,可以将结果类型转换为Double,如下所示:`return(Double)engine.eval(foo);` (3认同)
  • 还要注意浮点数学。0.1+0.2 将评估为 0.30000000000000004 (3认同)
  • 请注意,如果未过滤字符串输入,则 100% 拒绝服务。就像如果 `exit();` 被传递,它将在 java 中退出。 (3认同)
  • @Boann,我请你给我一个关于你说的话的参考.(确保100%) (2认同)
  • @EJP:没错。但是很多人不知道,JavaScript 只是浮点数。我认为,使用 JjavaScript 进行计算是一个坏主意。 (2认同)
  • @CardinalSystem 我不明白问题是什么?4^13 等于 6.7108864E7 ... 或者 java 方法 Math.pow 有问题。x 的平方根是 x^0.5。如果需要,可以根据自己的需要随意扩展 javaluator,javaluator 的站点上有一个教程。 (2认同)
  • 您可以创建RegEx来仅使用数学字符来计算表达式,例如`Pattern mathExpressionPattern = Pattern.compile(“ [0-9,。\\(\\)\\ / \\-\\ ++ \\ * \\ ^] +“)` (2认同)
  • @ahmednabil88,这是因为在javascript中“0”+数字是八进制数,所以024在表达式“024 + 862”中被解释为十进制的20 (2认同)
  • Nashorn(在此解决方案中使用)已被弃用。我不会推荐这个解决方案用于新代码。 (2认同)

Boa*_*ann 211

我已经eval为算术表达式编写了这个方法来回答这个问题.它执行加法,减法,乘法,除法,取幂(使用^符号)和一些基本函数sqrt.它支持使用(...进行分组),它可以使运算符优先级关联性规则正确.

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 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();
                eat(')');
            } 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);
                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();
}
Run Code Online (Sandbox Code Playgroud)

例:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));
Run Code Online (Sandbox Code Playgroud)

输出:7.5 (这是正确的)


解析器是递归下降解析器,因此内部对其语法中的每个级别的运算符优先级使用单独的解析方法.我保持简短,所以很容易修改,但这里有一些想法,你可能想要扩展它:

  • 变量:

    通过查找传递给eval方法的变量表中的名称(如a),可以轻松更改读取函数名称的解析器位以处理自定义变量Map<String,Double> variables.

  • 单独的编译和评估:

    如果在添加对变量的支持后,您希望使用已更改的变量对相同的表达式进行数百万次计算,而不是每次都进行解析,该怎么办?这是可能的.首先定义用于评估预编译表达式的接口:

    @FunctionalInterface
    interface Expression {
        double eval();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    现在更改返回doubles的所有方法,因此它们返回该接口的实例.Java 8的lambda语法非常适用于此.其中一个更改方法的示例:

    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;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这构建了一个Expression表示编译表达式的对象的递归树(一个抽象语法树).然后你可以编译一次并用不同的值重复评估它:

    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());
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 不同的数据类型:

    而不是double,你可以改变评估者使用更强大的东西BigDecimal,或者实现复杂数字或有理数(分数)的类.您甚至可以使用Object,允许在表达式中混合使用某种数据类型,就像真正的编程语言一样.:)


此答案中的所有代码都发布到公共领域.玩得开心!

  • 不错的算法,从它开始我设法实现和逻辑运算符。我们为函数创建了单独的类来评估函数,所以就像你对变量的想法一样,我创建了一个包含函数的映射并关注函数名称。每个函数都实现了一个带有方法 eval (T rightOperator , T leftOperator) 的接口,因此我们可以随时添加功能而无需更改算法代码。让它与泛型类型一起工作是个好主意。谢谢! (2认同)
  • 谢谢你的片段!基于此,我创建了一个解析器,它可以将表达式与 =、&lt;、&gt;、!= 等进行比较,并且也可以应用逻辑运算符 AND 和 OR。 (2认同)

Gre*_*ill 30

解决这个问题的正确方法是使用词法分析器解析器.您可以自己编写这些的简单版本,或者这些页面也有Java词法分析器和解析器的链接.

创建递归下降解析器是一个非常好的学习练习.


Ler*_*gan 23

对于我的大学项目,我一直在寻找支持基本公式和更复杂方程(特别是迭代运算符)的解析器/求值器.我找到了非常好的JAVA和.NET开源库,名为mXparser.我将举几个例子来对语法有所了解,如需进一步说明,请访问项目网站(特别是教程部分).

http://mathparser.org/

http://mathparser.org/mxparser-tutorial/

http://mathparser.org/api/

几个例子

1 - 简单的furmula

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()
Run Code Online (Sandbox Code Playgroud)

2 - 用户定义的参数和常量

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()
Run Code Online (Sandbox Code Playgroud)

3 - 用户定义的功能

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()
Run Code Online (Sandbox Code Playgroud)

4 - 迭代

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()
Run Code Online (Sandbox Code Playgroud)

最好的祝福

  • 在[此处](https://mvnrepository.com/artifact/org.mariuszgromada.math/MathParser.org-mXparser)查找 Maven 版本。 (2认同)
  • 刚刚找到解决方案,表达式.setSlientMode() (2认同)

Tan*_*vir 18

HERE是GitHub上另一个名为EvalEx的开源库.

与JavaScript引擎不同,此库专注于仅评估数学表达式.此外,该库是可扩展的,并支持使用布尔运算符和括号.


mar*_*ner 14

您还可以尝试使用BeanShell解释器:

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));
Run Code Online (Sandbox Code Playgroud)


DAB*_*DAB 14

如果Java应用程序已经访问数据库,则可以轻松地评估表达式,而无需使用任何其他JAR.

有些数据库要求您使用虚拟表(例如,Oracle的"双"表),而其他数据库则允许您在不从任何表"选择"的情况下评估表达式.

例如,在Sql Server或Sqlite中

select (((12.10 +12.0))/ 233.0) amount
Run Code Online (Sandbox Code Playgroud)

在Oracle中

select (((12.10 +12.0))/ 233.0) amount from dual;
Run Code Online (Sandbox Code Playgroud)

使用DB的优点是您可以同时评估多个表达式.此外,大多数DB都允许您使用高度复杂的表达式,并且还可以根据需要调用许多额外的函数.

但是,如果需要单独评估许多单个表达式,特别是当DB位于网络服务器上时,性能可能会受到影响.

以下通过使用Sqlite内存数据库在一定程度上解决了性能问题.

这是Java中的一个完整的工作示例

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();
Run Code Online (Sandbox Code Playgroud)

当然,您可以扩展上面的代码以同时处理多个计算.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");
Run Code Online (Sandbox Code Playgroud)

  • 跟SQL注入问好! (4认同)
  • @cyberz如果您使用上面的示例,Sqlite将在内存中创建一个临时DB.见http://stackoverflow.com/questions/849679/temporary-in-memory-database-in-sqlite (3认同)

Bra*_*rks 9

本文指出了3种不同的方法,一种是来自Apache的JEXL,并允许包含对java对象的引用的脚本.

  • 请总结文章中的信息,以防它的链接被破坏. (4认同)
  • 实际上,JEXL 很慢(使用 beans 的内省),存在多线程性能问题(全局缓存) (2认同)

Fah*_*ail 7

另一种方法是使用Spring Expression Language或SpEL,它可以在评估数学表达式的同时做更多的工作,因此可能有点过分.您不必使用Spring框架来使用此表达式库,因为它是独立的.复制SpEL文档中的示例:

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0
Run Code Online (Sandbox Code Playgroud)

阅读更简洁的规划环境地政司的例子在这里和完整的文档在这里


Sco*_*ion 6

这是另一个有趣的替代方法 https://github.com/Shy-Ta/expression-evaluator-demo

用法非常简单,可以完成工作,例如:

  ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");  
  assertEquals(BigDecimal.valueOf(11), evalExpr.eval()); 
Run Code Online (Sandbox Code Playgroud)


小智 6

如果我们要实现它,那么我们可以使用以下算法: -

  1. 虽然还有令牌需要阅读,

    1.1获取下一个令牌.1.2如果令牌是:

    1.2.1数字:将其推送到值堆栈.

    1.2.2变量:获取其值,并推入值栈.

    1.2.3左括号:将其推入操作员堆栈.

    1.2.4右括号:

     1 While the thing on top of the operator stack is not a 
       left parenthesis,
         1 Pop the operator from the operator stack.
         2 Pop the value stack twice, getting two operands.
         3 Apply the operator to the operands, in the correct order.
         4 Push the result onto the value stack.
     2 Pop the left parenthesis from the operator stack, and discard it.
    
    Run Code Online (Sandbox Code Playgroud)

    1.2.5运营商(称之为thisOp):

     1 While the operator stack is not empty, and the top thing on the
       operator stack has the same or greater precedence as thisOp,
       1 Pop the operator from the operator stack.
       2 Pop the value stack twice, getting two operands.
       3 Apply the operator to the operands, in the correct order.
       4 Push the result onto the value stack.
     2 Push thisOp onto the operator stack.
    
    Run Code Online (Sandbox Code Playgroud)
  2. 当操作员堆栈不为空时,1从操作员堆栈中弹出操作员.2弹出值栈两次,得到两个操作数.3以正确的顺序将操作符应用于操作数.4将结果推送到值堆栈.

  3. 此时操作符堆栈应该为空,并且值堆栈中应该只有一个值,这是最终结果.

  • 这是[迪杰斯特拉调度场算法]的uncredited博览会(https://en.wikipedia.org/wiki/Shunting-yard_algorithm).信用到期的信用. (2认同)

Boz*_*zho 5

好像JEP应该做的工作


小智 5

import java.util.*;

public class check { 
   int ans;
   String str="7 + 5";
   StringTokenizer st=new StringTokenizer(str);

   int v1=Integer.parseInt(st.nextToken());
   String op=st.nextToken();
   int v2=Integer.parseInt(st.nextToken());

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
Run Code Online (Sandbox Code Playgroud)


Sar*_*ana 5

现在回答为时已晚,但我遇到了同样的情况来评估 Java 中的表达式,这可能对某人有所帮助

MVEL对表达式进行运行时评估,我们可以在其中编写一个 java 代码String来对其进行评估。

    String expressionStr = "x+y";
    Map<String, Object> vars = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);
Run Code Online (Sandbox Code Playgroud)


Bru*_*uce 5

使用 JDK1.6 的 Javascript 引擎和代码注入处理来尝试以下示例代码。

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class EvalUtil {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static void main(String[] args) {
    try {
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || 5 >3 "));
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || true"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public Object eval(String input) throws Exception{
    try {
        if(input.matches(".*[a-zA-Z;~`#$_{}\\[\\]:\\\\;\"',\\.\\?]+.*")) {
            throw new Exception("Invalid expression : " + input );
        }
        return engine.eval(input);
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
 }
}
Run Code Online (Sandbox Code Playgroud)


Rom*_*rra 5

这实际上是对@Boann 给出的答案的补充。它有一个小错误,导致“-2 ^ 2”给出错误结果 -4.0。问题在于在 his 中求幂的点。只需将求幂移至 parseTerm() 块即可,一切都会好起来的。看看下面的内容,这是@Boann 的答案稍作修改。修改在评论里。

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 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 if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                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();
                eat(')');
            } 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);
                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 -> This is causing a bit of problem

            return x;
        }
    }.parse();
}
Run Code Online (Sandbox Code Playgroud)