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

How to evaluate a math expression given in string form?

提问人:Shah 提问时间:8/6/2010 最后编辑:PshemoShah 更新时间:4/14/2023 访问量:614323

问:

我正在尝试编写一个 Java 例程来计算数学表达式,例如:String

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

我想避免很多 if-then-else 语句。 我该怎么做?

Java 字符串 数学

评论

2赞 Raedwald 12/10/2014
你们允许什么样的表达方式?只有单个运算符表达式?是否允许使用括号?
10赞 fasseg 12/29/2010
我最近写了一个名为exp4j的数学表达式解析器,它是在apache许可下发布的,你可以在这里查看它:objecthunter.net/exp4j
4赞 Ritesh 1/21/2016
还可以看看 Dijkstra 的双栈算法
1赞 Andrew Li 9/21/2016
Java 中是否有 eval() 函数的可能重复项?
3赞 Martin Spamer 10/13/2018
这怎么可能被认为过于宽泛?Dijkstra 的评估是显而易见的解决方案 en.wikipedia.org/wiki/Shunting-yard_algorithm

答:

4赞 BruteForce 8/6/2010 #1

我认为无论你以何种方式做到这一点,它都会涉及很多条件语句。但是对于示例中的单个操作,您可以将其限制为 4 个 if 语句,如下所示

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

当您想处理“4+5*6”等多个操作时,它会变得更加复杂。

如果你正在尝试构建一个计算器,那么我会将计算的每个部分(每个数字或运算符)分开传递,而不是作为一个字符串。

评论

2赞 user207421 6/16/2016
一旦您必须处理多个操作,运算符优先级,括号,它就会变得更加复杂......事实上,任何表征真实算术表达式的东西。你不能从这种技术开始到达那里。
36赞 Greg Hewgill 8/6/2010 #2

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

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

6赞 Bozho 8/6/2010 #3

看来 JEP 应该做这项工作

416赞 RealHowTo 8/6/2010 #4

在 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));
    } 
}

评论

61赞 Jean-Marc Astesana 8/29/2012
那里似乎有一个大问题;它执行脚本,而不是计算表达式。需要明确的是,engine.eval(“8;40+2“),输出 42 !如果你想要一个也检查语法的表达式解析器,我刚刚完成了一个(因为我没有找到适合我需求的东西):Javaluator
4赞 Ben Visness 4/3/2014
顺便说一句,如果需要在代码中的其他位置使用此表达式的结果,则可以将结果类型转换为 Double,如下所示:return (Double) engine.eval(foo);
57赞 Boann 9/21/2015
安全说明:切勿在具有用户输入的服务器上下文中使用它。执行的 JavaScript 可以访问所有 Java 类,从而无限制地劫持您的应用程序。
5赞 partho 2/27/2016
@Boann,我请你给我一个关于你所说的话的参考。(确保 100%)
31赞 Boann 2/27/2016
@partho -- 将通过 JavaScript 将文件写入(默认)程序的当前目录new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");
5赞 stone 2/15/2012 #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; }
   //.........
}
11赞 Brad Parks 7/3/2012 #6

本文讨论了各种方法。以下是文章中提到的 2 种关键方法:

来自 Apache 的 JEXL

允许包含对 Java 对象的引用的脚本。

// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );
 
// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );
 
// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);

使用 JDK 中嵌入的 javascript 引擎:

private static void jsEvalWithVariable()
{
    List<String> namesList = new ArrayList<String>();
    namesList.add("Jill");
    namesList.add("Bob");
    namesList.add("Laureen");
    namesList.add("Ed");
 
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
 
    jsEngine.put("namesListKey", namesList);
    System.out.println("Executing in script environment...");
    try
    {
      jsEngine.eval("var x;" +
                    "var names = namesListKey.toArray();" +
                    "for(x in names) {" +
                    "  println(names[x]);" +
                    "}" +
                    "namesListKey.add(\"Dana\");");
    }
    catch (ScriptException ex)
    {
        ex.printStackTrace();
    }
}

评论

4赞 DJClayworth 10/11/2016
请总结文章中的信息,以防链接损坏。
0赞 Brad Parks 3/20/2019
我已经升级了答案,以包含文章中的相关内容
2赞 Nishi 3/4/2020
在实践中,JEXL 速度很慢(使用 Bean 的内省),多线程(全局缓存)存在性能问题
0赞 Brad Parks 3/5/2020
很高兴知道@Nishi!- 我的用例是在实时环境中调试东西,但不是正常部署的应用程序的一部分。
17赞 marciowerner 10/12/2012 #7

您也可以尝试 BeanShell 解释器:

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

评论

1赞 Hanni 2/7/2016
你能告诉我如何在adnroid Studio中使用BeanShell吗?
1赞 marciowerner 2/8/2016
Hanni - 这篇文章可能会帮助您将 BeanShell 添加到您的 androidstudio 项目中: stackoverflow.com/questions/18520875/...
2赞 konxie 10/18/2012 #8

像这样的东西怎么样:

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

并相应地对其他所有数学运算符执行类似操作。

评论

9赞 Daniel Nuriyev 4/25/2014
您应该阅读有关编写高效的数学表达式解析器的信息。它有一种计算机科学方法论。例如,看看 ANTLR。如果你仔细思考你写的东西,你会发现像 (a+b/-c)*(e/f) 这样的东西不适合你的想法,或者代码会超级愚蠢、肮脏和低效。
22赞 Tanvir 9/4/2013 #9

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

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

评论

0赞 paarth batra 4/26/2018
这没关系,但是当我们尝试将 5 或 10 的倍数的值相乘时会失败,例如 65 * 6 的结果为 3.9E+2 ...
0赞 paarth batra 4/26/2018
.但是有一种方法可以通过将它转换为 int 来解决这个问题,即 int output = (int) 65*6 现在结果为 390
1赞 DavidBittner 6/6/2018
澄清一下,这不是库的问题,而是将数字表示为浮点值的问题。
0赞 einUsername 3/7/2020
这个库真的很好。@paarth batra Casting to int 将删除所有小数点。请改用: expression.eval().toPlainString();
16赞 Faheem Sohail 2/26/2014 #10

另一种方法是使用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
317赞 Boann 10/7/2014 #11

我为算术表达式编写了这个方法来回答这个问题。它执行加法、减法、乘法、除法、幂(使用符号)和一些基本函数,如 .它支持使用 ...(...)进行分组,并正确获取运算符优先级关联性规则。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 `(` 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();
}

例:

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

输出:7.5(正确)


解析器是一个递归下降解析器,因此在内部对其语法中的每个运算符优先级使用单独的解析方法。我故意保持简短,但这里有一些你可能想扩展它的想法:

  • 变量:

    读取函数名称的解析器位也可以很容易地更改以处理自定义变量,方法是在传递给方法的变量表中查找名称,例如 .evalMap<String,Double> variables

  • 单独编译和评估:

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

      @FunctionalInterface
      interface Expression {
          double eval();
      }
    

    现在,要将原始的“eval”函数重新设计为“parse”函数,请更改所有返回 s 的方法,以便它们返回该接口的实例。Java 8 的 lambda 语法非常适合此目的。更改的方法之一的示例:double

      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;
              }
          }
      }
    

    这将构建一个递归的对象树,该树表示已编译的表达式(抽象语法树)。然后,您可以编译一次,并使用不同的值重复计算它: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());
          }
      }
    
  • 不同的数据类型:

    您可以更改赋值器以使用更强大的功能,例如 ,或实现复数或有理数(分数)的类。您甚至可以使用 ,允许在表达式中混合使用一些数据类型,就像真正的编程语言一样。:)doubleBigDecimalObject


此答案中的所有代码都已发布到公共领域。玩得愉快!

评论

4赞 Vasile Bors 7/24/2016
不错的算法,从它开始,我设法实现和逻辑运算符。我们为函数创建了单独的类来评估函数,所以就像你对变量的想法一样,我创建了一个包含函数的映射并注意函数名称。每个函数都使用方法 eval (T rightOperator , T leftOperator) 实现一个接口,因此我们可以随时添加功能而无需更改算法代码。让它与泛型类型一起使用是个好主意。谢谢!
1赞 iYonatan 7/24/2016
你能解释一下这个算法背后的逻辑吗?
1赞 Vasile Bors 7/24/2016
我试着描述一下我从 Boann 编写的代码中理解的内容,以及描述 wiki 的示例。这种算法的逻辑从操作顺序的规则开始。1. 操作员标志 |变量评估 |函数调用 |括号(子表达式);2. 幂;3.乘法、除法;4、加法、减法;
1赞 Vasile Bors 7/24/2016
算法方法按每个级别的操作顺序划分如下:parseFactor = 1。操作员标志 |变量评估 |函数调用 |括号(子表达式);2. 幂;parseTerms = 3。乘法、除法;parseExpression = 4。加法、减法。该算法,调用方法的顺序相反(parseExpression -> parseTerms -> parseFactor -> parseExpression(用于子表达式)),但每个方法到第一行调用方法到下一个级别,所以整个方法的执行顺序实际上都是正常的顺序操作。
2赞 janhink 1/4/2017
谢谢你的片段!基于此,我创建了一个解析器,可以将表达式与 =、<、>、!= 等进行比较,也可以应用逻辑运算符 AND 和 OR。
8赞 Scorpion 1/1/2015 #12

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

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

  ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");  
  assertEquals(BigDecimal.valueOf(11), evalExpr.eval()); 
2赞 Emmanuel John 7/6/2015 #13

可以使用 Djikstra 的分流场算法将中缀表示法中的任何表达式字符串转换为后缀表示法。然后,该算法的结果可以用作后缀算法的输入,该算法返回表达式的结果。

评论

0赞 Martin Frank 10/22/2022
那个链接已经死了
9赞 Prashant Gautam 9/18/2015 #14

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

  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.
    

    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.
    
  2. 虽然算子堆栈不为空, 1 从运算符堆栈中弹出运算符。 2 弹出值堆栈两次,得到两个操作数。 3 按正确的顺序将运算符应用于操作数。 4 将结果推送到值堆栈上。

  3. 此时,运算符堆栈应为空,并且值 堆栈中应该只有一个值,即最终结果。

评论

5赞 user207421 6/16/2016
这是对 Dijkstra 调车场算法的未署名的阐述。应得的信用。
5赞 Saravana 10/19/2015 #15

现在回答为时已晚,但我遇到了同样的情况来评估 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);

评论

0赞 xWAV92OdD445CbzcZAEyunk2M06fJi 4/16/2019
我搜索了一下,发现这里还处理了一些额外的算术函数 github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/......
0赞 skmaran.nr.iras 12/16/2019
太棒了!它挽救了我的一天。谢谢
4赞 Laurent Magnin 10/29/2015 #16

你可以看看 Symja 框架

ExprEvaluator util = new ExprEvaluator(); 
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30" 

请注意,可以计算更复杂的表达式:

// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2
47赞 Leroy Kegan 4/2/2016 #17

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

https://mathparser.org/

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

https://mathparser.org/api/

例子很少

1 - 简单的疣草

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()

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()

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()

4 - 迭代

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()

最近发现 - 如果您想尝试语法(并查看高级用例),您可以下载由 mXparser 提供支持的 Scalar Calculator 应用程序

评论

1赞 Trynkiewicz Mariusz 2/6/2019
到目前为止,这是最好的数学库;易于启动,易于使用和可扩展。绝对应该是最佳答案。
2赞 izogfif 3/12/2019
在此处查找 Maven 版本。
1赞 lulijun 3/14/2019
我发现 mXparser 无法识别非法公式,例如,“0/0”将得到“0”的结果。我该如何解决这个问题?
2赞 lulijun 3/14/2019
刚刚找到解决方案,expression.setSlientMode()
0赞 Gamebuster19901 10/19/2022
请注意:mXParser 不再是开源的。
2赞 Stefan Haustein 4/17/2016 #18

另一种选择:https://github.com/stefanhaustein/expressionparser

我已经实现了这一点,以便有一个简单但灵活的选项来允许两者:

上面链接的 TreeBuilder 是执行符号派生的 CAS 演示包的一部分。还有一个BASIC解释器示例,我已经开始使用它构建一个TypeScript解释器

16赞 DAB 4/23/2016 #19

如果您的 Java 应用程序已经访问了数据库,则可以轻松计算表达式,而无需使用任何其他 JAR。

一些数据库要求你使用虚拟表(例如,Oracle 的“双重”表),而其他数据库则允许你计算表达式,而无需从任何表中“选择”。

例如,在 Sql Server 或 Sqlite 中

select (((12.10 +12.0))/ 233.0) amount

和在 Oracle 中

select (((12.10 +12.0))/ 233.0) amount from dual;

使用数据库的优点是可以同时计算多个表达式。此外,大多数数据库将允许您使用高度复杂的表达式,并且还具有许多可以根据需要调用的额外函数。

但是,如果需要单独计算许多单个表达式,特别是当数据库位于网络服务器上时,性能可能会受到影响。

下面通过使用 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();

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

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

评论

10赞 cyberz 4/24/2017
向 SQL 注入问好!
0赞 DAB 4/24/2017
这取决于您将数据库用于什么目的。如果你想确定,你可以很容易地创建一个空的sqlite数据库,专门用于数学评估。
4赞 DAB 4/24/2017
@cyberz 如果你使用我上面的例子,Sqlite将在内存中创建一个临时数据库。查看 stackoverflow.com/questions/849679/...
4赞 chejaras 6/8/2016 #20
package ExpressionCalculator.expressioncalculator;

import java.text.DecimalFormat;
import java.util.Scanner;

public class ExpressionCalculator {

private static String addSpaces(String exp){

    //Add space padding to operands.
    //https://regex101.com/r/sJ9gM7/73
    exp = exp.replaceAll("(?<=[0-9()])[\\/]", " / ");
    exp = exp.replaceAll("(?<=[0-9()])[\\^]", " ^ ");
    exp = exp.replaceAll("(?<=[0-9()])[\\*]", " * ");
    exp = exp.replaceAll("(?<=[0-9()])[+]", " + "); 
    exp = exp.replaceAll("(?<=[0-9()])[-]", " - ");

    //Keep replacing double spaces with single spaces until your string is properly formatted
    /*while(exp.indexOf("  ") != -1){
        exp = exp.replace("  ", " ");
     }*/
    exp = exp.replaceAll(" {2,}", " ");

       return exp;
}

public static Double evaluate(String expr){

    DecimalFormat df = new DecimalFormat("#.####");

    //Format the expression properly before performing operations
    String expression = addSpaces(expr);

    try {
        //We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and
        //subtraction will be processed in following order
        int indexClose = expression.indexOf(")");
        int indexOpen = -1;
        if (indexClose != -1) {
            String substring = expression.substring(0, indexClose);
            indexOpen = substring.lastIndexOf("(");
            substring = substring.substring(indexOpen + 1).trim();
            if(indexOpen != -1 && indexClose != -1) {
                Double result = evaluate(substring);
                expression = expression.substring(0, indexOpen).trim() + " " + result + " " + expression.substring(indexClose + 1).trim();
                return evaluate(expression.trim());
            }
        }

        String operation = "";
        if(expression.indexOf(" / ") != -1){
            operation = "/";
        }else if(expression.indexOf(" ^ ") != -1){
            operation = "^";
        } else if(expression.indexOf(" * ") != -1){
            operation = "*";
        } else if(expression.indexOf(" + ") != -1){
            operation = "+";
        } else if(expression.indexOf(" - ") != -1){ //Avoid negative numbers
            operation = "-";
        } else{
            return Double.parseDouble(expression);
        }

        int index = expression.indexOf(operation);
        if(index != -1){
            indexOpen = expression.lastIndexOf(" ", index - 2);
            indexOpen = (indexOpen == -1)?0:indexOpen;
            indexClose = expression.indexOf(" ", index + 2);
            indexClose = (indexClose == -1)?expression.length():indexClose;
            if(indexOpen != -1 && indexClose != -1) {
                Double lhs = Double.parseDouble(expression.substring(indexOpen, index));
                Double rhs = Double.parseDouble(expression.substring(index + 2, indexClose));
                Double result = null;
                switch (operation){
                    case "/":
                        //Prevent divide by 0 exception.
                        if(rhs == 0){
                            return null;
                        }
                        result = lhs / rhs;
                        break;
                    case "^":
                        result = Math.pow(lhs, rhs);
                        break;
                    case "*":
                        result = lhs * rhs;
                        break;
                    case "-":
                        result = lhs - rhs;
                        break;
                    case "+":
                        result = lhs + rhs;
                        break;
                    default:
                        break;
                }
                if(indexClose == expression.length()){
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose);
                }else{
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose + 1);
                }
                return Double.valueOf(df.format(evaluate(expression.trim())));
            }
        }
    }catch(Exception exp){
        exp.printStackTrace();
    }
    return 0.0;
}

public static void main(String args[]){

    Scanner scanner = new Scanner(System.in);
    System.out.print("Enter an Mathematical Expression to Evaluate: ");
    String input = scanner.nextLine();
    System.out.println(evaluate(input));
}

}

评论

2赞 user207421 7/18/2016
不处理运算符优先级、多个运算符或括号。请勿使用。
0赞 deepakl.2000 5/24/2021
它是否也处理括号,你能修改代码来处理括号并除以零吗?
0赞 deepakl.2000 9/26/2021
@chejaras 它是否也处理括号,你能不能修改代码来处理括号和除以零。
5赞 Bruce 9/24/2016 #21

使用 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;
    }
 }
}
5赞 Romeo Sierra 12/22/2016 #22

这实际上是对@Boann给出的答案的补充。它有一个小错误,导致“-2 ^ 2”给出 -4.0 的错误结果。问题在于在他的中计算幂的点。只需将幂移动到 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();
}

评论

1赞 Radiodef 8/16/2018
-2^2 = -4其实是正常的,不是bug。它被分组为 .例如,在 Desmos 上尝试一下。您的代码实际上引入了几个错误。首先是不再从右到左分组。换句话说,应该是 group like,因为是右关联,但您的修改使它像 group like 。第二个是它应该具有比 和 更高的优先级,但您的修改会以相同的方式对待它。请参见 ideone.com/iN2mMa-(2^2)^2^3^22^(3^2)^(2^3)^2^*/
0赞 Romeo Sierra 8/16/2018
所以,你的建议是,幂最好保持在原来的位置,不是吗?
0赞 Radiodef 8/16/2018
是的,这就是我的建议。
4赞 Efi G 5/26/2017 #23

一个可以计算数学表达式的 Java 类:

package test;

public class Calculator {

    public static Double calculate(String expression){
        if (expression == null || expression.length() == 0) {
            return null;
        }
        return calc(expression.replace(" ", ""));
    }
    public static Double calc(String expression) {
        String[] containerArr = new String[]{expression};
        double leftVal = getNextOperand(containerArr);
        expression = containerArr[0];
        if (expression.length() == 0) {
            return leftVal;
        }
        char operator = expression.charAt(0);
        expression = expression.substring(1);

        while (operator == '*' || operator == '/') {
            containerArr[0] = expression;
            double rightVal = getNextOperand(containerArr);
            expression = containerArr[0];
            if (operator == '*') {
                leftVal = leftVal * rightVal;
            } else {
                leftVal = leftVal / rightVal;
            }
            if (expression.length() > 0) {
                operator = expression.charAt(0);
                expression = expression.substring(1);
            } else {
                return leftVal;
            }
        }
        if (operator == '+') {
            return leftVal + calc(expression);
        } else {
            return leftVal - calc(expression);
        }

    }
    
    private static double getNextOperand(String[] exp){
        double res;
        if (exp[0].startsWith("(")) {
            int open = 1;
            int i = 1;
            while (open != 0) {
                if (exp[0].charAt(i) == '(') {
                    open++;
                } else if (exp[0].charAt(i) == ')') {
                    open--;
                }
                i++;
            }
            res = calc(exp[0].substring(1, i - 1));
            exp[0] = exp[0].substring(i);
        } else {
            int i = 1;
            if (exp[0].charAt(0) == '-') {
                i++;
            }
            while (exp[0].length() > i && isNumber((int) exp[0].charAt(i))) {
                i++;
            }
            res = Double.parseDouble(exp[0].substring(0, i));
            exp[0] = exp[0].substring(i);
        }
        return res;
    }


    private static boolean isNumber(int c) {
        int zero = (int) '0';
        int nine = (int) '9';
        return (c >= zero && c <= nine) || c =='.';
    }

    public static void main(String[] args) {
        System.out.println(calculate("(((( -6 )))) * 9 * -1"));
        System.out.println(calc("(-5.2+-5*-5*((5/4+2)))"));

    }

}

评论

3赞 user207421 4/17/2018
未正确处理运算符优先级。有标准的方法可以做到这一点,这不是其中之一。
0赞 Efi G 4/22/2018
EJP,您能指出运算符优先级存在问题的地方吗?我完全同意这样一个事实,即这不是标准方法。标准方法在以前的帖子中已经提到过,我们的想法是展示另一种方法。
0赞 deepakl.2000 5/23/2021
我也在计算器中评估了 16+10×500−3,000÷50,答案应该是 4956。您的算法打印 -414
1赞 Efi G 5/24/2021
不确定逗号是否被处理。div 运算符是“/”而不是“÷”
0赞 deepakl.2000 9/26/2021
是的,它现在正在工作,你能帮我解决 stackoverflow.com/questions/6666169/ 类似的问题吗?
2赞 Manish 10/20/2017 #24

像 RHINO 或 NASHORN 这样的外部库可用于运行 javascript。javascript 可以在不解析字符串的情况下计算简单的公式。如果代码写得好,也不会对性能产生影响。 下面是 RHINO 的示例 -

public class RhinoApp {
    private String simpleAdd = "(12+13+2-2)*2+(12+13+2-2)*2";

public void runJavaScript() {
    Context jsCx = Context.enter();
    Context.getCurrentContext().setOptimizationLevel(-1);
    ScriptableObject scope = jsCx.initStandardObjects();
    Object result = jsCx.evaluateString(scope, simpleAdd , "formula", 0, null);
    Context.exit();
    System.out.println(result);
}
0赞 Md Adilur Rashid 8/20/2020 #25
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class test2 {
    public static void main(String[] args) throws ScriptException {
        String s = "10+2";
        ScriptEngineManager mn = new ScriptEngineManager();
        ScriptEngine en = mn.getEngineByName("js");
        Object result = en.eval(s);
        System.out.println(result);
    }
}

评论

0赞 h.Ebrahimi 2/10/2023
当我使用此代码时,我收到以下警告:nashorn 引擎计划从未来的 jdk 版本中删除
0赞 Nagappa L M 10/31/2022 #26

我已经完成了迭代解析和分流 Yard 算法的使用,我真的很喜欢开发表达式计算器,您可以在这里找到所有代码

https://github.com/nagaraj200788/JavaExpressionEvaluator

有 73 个测试用例,甚至适用于 Bigintegers、Bigdecimals

支持所有关系表达式、算术表达式以及两者的组合。 甚至支持三元算子。

添加了增强功能以支持有符号数字,例如 -100+89 它很有意义,有关详细信息,请查看 TokenReader.isUnaryOperator() 方法,我更新了上面链接中的代码

0赞 michal.jakubeczy 4/14/2023 #27

如果有人需要精确的十进制数,我做了一个修改,它使用 而不是 .BigDecimaldouble

我从 Boann 的回答中获得了灵感,并将其修改为 .BigDecimal

public static BigDecimal eval(final String str, MathContext mathContext) {
    if (ObjectUtils.isEmpty(str)) {
        return null;
    }

    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;
        }

        BigDecimal parse() {
            nextChar();
            BigDecimal 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

        BigDecimal parseExpression() {
            BigDecimal x = parseTerm();
            for (;;) {
                if      (eat('+')) x = x.add(parseTerm()); // addition
                else if (eat('-')) x = x.subtract(parseTerm()); // subtraction
                else return x;
            }
        }

        BigDecimal parseTerm() {
            BigDecimal x = parseFactor();
            for (;;) {
                if      (eat('*')) x = x.multiply(parseFactor()); // multiplication
                else if (eat('/')) x = x.divide(parseFactor(), mathContext); // division
                else return x;
            }
        }

        BigDecimal parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return parseFactor().negate(); // unary minus

            BigDecimal 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 = new BigDecimal(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 ("sqrt".equals(func)) {
                    x = x.sqrt(mathContext);
                } else {
                    throw new RuntimeException("Unknown function: " + func);
                }
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            if (eat('^')) x = x.pow(parseFactor().intValue(), mathContext); // exponentiation

            return x;
        }
    }.parse();
}

按以下方式使用它:

// set precision and rounding mode
MathContext mathContext = new MathContext(10, RoundingMode.HALF_UP);
BigDecimal result = YourLib.eval("33+33", mathContext);