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


318

我正在尝试编写Java例程,以从String类似以下值的值评估简单的数学表达式:

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

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


7
我最近写了一个名为exp4j的数学表达式解析器,该解析器是根据apache许可发布的,您可以在这里查看:objecthunter.net/exp4j
fasseg 2010年

2
您允许使用哪种表达方式?仅单个运算符表达式?可以使用括号吗?
Raedwald 2014年

3
还可以看看Dijkstra的两栈算法
Ritesh


3
如何将其视为范围太广?迪克斯特拉(Dijkstra)的评估是显而易见的解决方案, 网址为en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Answers:


376

使用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));
    } 
}

52
似乎那里有个大问题。它执行脚本,而不计算表达式。需要明确的是,engine.eval(“ 8; 40 + 2”)输出42!如果您想要一个表达式解析器也可以检查语法,那么我已经完成了一个(因为没有找到适合我的需求):Javaluator
Jean-Marc Astesana

4
附带说明一下,如果您需要在代码的其他位置使用此表达式的结果,则可以将结果类型转换为Double,如下所示: return (Double) engine.eval(foo);
Ben Visness 2014年

38
安全说明:切勿在带有用户输入的服务器上下文中使用此功能。执行的JavaScript可以访问所有Java类,因此可以无限制地劫持您的应用程序。
Boann

3
@Boann,我要求您提供关于您所说内容的参考。(确保100%)
partho

17
@partho- new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");将通过JavaScript将文件写入(默认情况下)程序的当前目录
Boann

236

我已经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();
}

例:

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

输出:7.5 (正确)


该解析器是递归下降式解析器,因此在内部对其语法的每个级别的运算符优先级使用单独的解析方法。我把它简短了一下,所以很容易修改,但是下面是一些您可能想扩展它的想法:

  • 变量:

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

  • 单独的编译和评估:

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

    @FunctionalInterface
    interface Expression {
        double eval();
    }
    

    现在更改所有返回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;
            }
        }
    }
    

    这将构建一个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());
        }
    }
    
  • 不同的数据类型:

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


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


1
不错的算法,从它开始,我设法实现了隐式和逻辑运算符。我们为函数创建了单独的类来评估函数,因此就像您对变量的想法一样,我创建了一个具有函数的映射并照顾函数名。每个函数都使用方法eval(T rightOperator和T leftOperator)来实现一个接口,因此任何时候我们都可以在不更改算法代码的情况下添加功能。使其与泛型类型一起使用是一个好主意。谢谢!
Vasile Bors

1
您能解释一下该算法背后的逻辑吗?
iYonatan

1
我试图描述一下我从Boann编写的代码中了解的内容,并举例说明Wiki。这种算法的逻辑从操作顺序规则开始。1.操作员标志| 变量评估| 函数调用| 括号(子表达式);2.求幂 3.乘法,除法;4.加,减;
Vasile Bors

1
算法方法按操作顺序的每个级别划分如下:parseFactor = 1。变量评估| 函数调用| 括号(子表达式);2.求幂 parseTerms = 3.乘法,除法;parseExpression =4。加,减。该算法以相反的顺序调用方法(parseExpression-> parseTerms-> parseFactor-> parseExpression(用于子表达式)),但是第一行的每个方法都将方法调用到下一级,因此整个执行顺序方法将是实际上是正常的操作顺序。
Vasile Bors

1
例如,parseExpression方法在double x = parseTerm(); 评估for (;;) {...}实际订单级别(加法,减法)的连续操作之后, 评估左运算符。和parseTerm方法中的逻辑相同。parseFactor没有下一个级别,因此仅对方法/变量进行评估,或者在使用括号的情况下-评估子表达式。该boolean eat(int charToEat)方法检查当前光标字符与charToEat字符是否相等,如果相等则返回true并将光标移至下一个字符,我为此使用名称“ accept”。
Vasile Bors

34

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

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


26

对于我的大学项目,我一直在寻找一种既支持基本公式又支持更复杂方程式(特别是迭代运算符)的解析器/评估器。我发现了非常好的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 应用程序

最好的祝福


到目前为止,这是目前最好的数学库。易于启动,易于使用且可扩展。绝对应该是最佳答案。
Trynkiewicz Mariusz,

在这里找到Maven版本。
izogfif

我发现mXparser无法识别非法公式,例如,“ 0/0”将得到结果为“ 0”。我该如何解决这个问题?
lulijun

刚刚找到了解决方案expression.setSlientMode()
lulijun

20

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

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


没关系,但是当我们尝试乘以5或10的倍数的值时失败,例如65 * 6导致3.9E + 2 ...
paarth batra

。但是有一种方法可以通过将其强制转换为int来解决,即int output =(int)65 * 6现在将产生390
paarth batra

1
需要澄清的是,这不是库的问题,而是数字作为浮点值的表示的问题。
DavidBittner

这个图书馆真的很好。@paarth batra强制转换为int将删除所有小数点。改用它:expression.eval()。toPlainString();
einUsername


14

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

一些数据库要求您使用虚拟表(例如,Oracle的“双重”表),而其他数据库将允许您在不从任何表“选择”的情况下评估表达式。

例如,在Sql Server或Sqlite中

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

和在甲骨文

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

使用数据库的好处是您可以同时评估多个表达式。同样,大多数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();

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

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

5
向SQL注入打个招呼!
cyberz

这取决于您使用数据库的目的。如果您想确定的话,可以轻松地创建一个空的sqlite DB,专门用于数学评估。
DAB

4
@cyberz如果使用上面的示例,Sqlite将在内存中创建一个临时数据库。参见stackoverflow.com/questions/849679/…–
DAB

11

本文讨论了各种方法。这是本文中提到的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 '16

我已经升级了答案,以包括文章中的相关内容
布拉德·帕克斯

1
实际上,JEXL速度很慢(使用bean的自省功能),在多线程(全局缓存)方面存在性能问题
Nishi

很高兴认识@Nishi!-我的用例是在现场环境中调试东西,但不属于正常部署的应用程序。
布拉德·帕克斯

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

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


8

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

  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. 此时,运算符堆栈应该为空,值堆栈中应该只有一个值,这是最终结果。


3
这是Dijkstra调车场算法的未经信用的说明。贷方到期的贷方。
罗恩侯爵,



4

我认为无论采用哪种方式,都会涉及很多条件语句。但是对于示例中的单个操作,如果语句中包含类似以下内容,则可以将其限制为4

String math = "1+4";

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

当您要处理“ 4 + 5 * 6”之类的多种操作时,它将变得更加复杂。

如果您要构建一个计算器,那么我会尽量将计算的每个部分(每个数字或运算符)分开传递,而不是作为一个单独的字符串传递。


2
一旦您必须处理多个运算,运算符优先级,括号等等,实际上就变得更加复杂了,实际上任何具有真实算术表达式特征的东西。从这种技术开始您无法到达那里。
罗恩侯爵,

4

回答为时已晚,但我遇到了同样的情况,需要用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);

我搜寻了一下,发现这里还处理了一些其他的算术函数github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…–
the codefather

好厉害!它挽救了我的一天。谢谢
Sarika.S

4

您可以看一下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

4

使用具有代码注入处理功能的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;
    }
 }
}

4

这实际上是对@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();
}

-2^2 = -4实际上是正常的,不是错误。它被分组为-(2^2)例如,在Desmos上尝试。您的代码实际上引入了几个错误。首先是^不再从右到左分组。换句话说,由于是右关联,因此2^3^2应该分组为,但是您所做的修改将其分组为。第二个是应该具有比和更高的优先级,但是您的修改将优先级相同。请参阅ideone.com/iN2mMa2^(3^2)^(2^3)^2^*/
Radiodef

因此,您的建议是将幂运算更好地保留在原来的位置上,不是吗?
罗密欧·塞拉

是的,这就是我的建议。
Radiodef

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

}


1
不处理运算符优先级,多个运算符或括号。不使用。
洛恩侯爵,

2

这样的事情怎么样:

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
您应该阅读有关编写高效的数学表达式解析器的信息。有一种计算机科学方法论。以ANTLR为例。如果您对所写内容进行了认真的思考,您会发现(a + b / -c)*(e / f)之类的东西将无法满足您的想法,或者代码会变得超级笨拙且效率低下。
Daniel Nuriyev 2014年



2

一个可以评估数学表达式的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) {

        if (expression.startsWith("(") && expression.endsWith(")")) {
            return calc(expression.substring(1, expression.length() - 1));
        }
        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)))"));

    }

}

2
无法正确处理运算符优先级。有标准的方法可以做到这一点,但这不是其中一种。
罗恩侯爵

EJP,能否请您指出运算符优先级存在问题的地方?我完全同意这不是标准方法。在以前的帖子中已经提到了标准方法,其目的是显示另一种方法。
Efi G

2

可以使用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);
}

2
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; }
   //.........
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.