我正在尝试编写Java例程,以根据String值评估简单的数学表达式:
"5+3"
"10-40"
"10*3"
我想避免很多if-then-else语句。
我怎样才能做到这一点?
我最近写了一个名为exp4j的数学表达式解析器,该解析器是根据apache许可发布的,您可以在这里查看:objecthunter.net/exp4j
您允许使用哪种表达方式? 仅单个运算符表达式? 可以使用括号吗?
还看一下Dijkstras两层算法
Java中是否存在eval()函数的可能重复项?
@fasseg,该库非常棒,但是,它似乎只处理数值表达式,如果我写错了,请纠正我。 我有一个需要检查表达式中字符串是否相等的要求,请您提出建议!
如何将其视为范围太广? dijkstras评估是显而易见的解决方案,网址为en.wikipedia.org/wiki/Shunting-yard_algorithm
使用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));
}
}
似乎那里有个大问题。它执行脚本,不计算表达式。需要明确的是,engine.eval(" 8; 40 + 2")输出42!如果您想要一个表达式解析器也检查语法,我就完成了一个(因为没有找到适合我的需求):Javaluator。
附带说明一下,如果您需要在代码的其他位置使用此表达式的结果,则可以将结果类型转换为Double,如下所示:return (Double) engine.eval(foo);
安全说明:切勿在带有用户输入的服务器上下文中使用此功能。执行的JavaScript可以访问所有Java类,因此可以无限制地劫持您的应用程序。
getEngineByName("JavaScript")在这里的含义是什么?
@ partho,getEngineByName()用于查找并创建给定名称(例如js,rhino,JavaScript,javascript,ECMAScript,ecmascript)的ScriptEngine。参见docs.oracle.com/javase/7/docs/api/javax/script/
@Boann,我要求您提供关于您所说内容的参考。(确保100%)
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter(hello.txt); f.write(UNLIMITED POWER!); f.close();");-将通过JavaScript将文件写入(默认情况下)程序的当前目录
也要注意浮点数学。 0.1 + 0.2将得出0.30000000000000004
请注意,如果未过滤字符串输入,则此功能100%开放拒绝服务。就像如果传递了exit();一样,它将在java中退出。
@ChristianKuetbach多数民众赞成在一个浮点数的属性,而不是执行脚本。
@EJP:是的。但是很多人并不知道JavaScript仅是浮点数。我认为,使用JjavaScript进行计算是一个坏主意。
在被零除的情况下,它不会抛出异常,并且由于它使用Java脚本引擎,因此会导致"无穷大"
@ Jean-MarcAstesana您如何通过平方根运算?另外,我通过了4^13并返回了6.7108864E7:/
@CardinalSystem我不明白是什么问题? 4 ^ 13等于6.7108864E7 ...或Java方法Math.pow有错误。 x的平方根是x ^ 0.5。如果需要,可以随意扩展javaluator,javaluators网站上有一个教程。
您可以创建一个RegEx来仅使用数学字符来计算表达式,例如Pattern mathExpressionPattern = Pattern.compile("[0-9,.\\(\\)\\\\-\\+\\*\\^]+" )
我已经为算术表达式编写了这个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方法的变量表(例如Mapvariables)中查找名称,可以轻松更改读取函数名称的解析器的位,以处理自定义变量。
单独的编译和评估:
如果在增加了对变量的支持之后,您想使用更改后的变量来评估同一表达式数百万次,而不是每次都对其进行解析,该怎么办?这是可能的。首先定义一个用于评估预编译表达式的接口:
@FunctionalInterface
interface Expression {
double eval();
}
现在更改所有返回double的方法,因此它们将返回该接口的实例。 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 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,允许在表达式中混合使用多种数据类型,就像真正的编程语言一样。 :)
此答案中的所有代码均已发布到公共领域。玩得开心! sub>
不错的算法,从它开始,我设法实现了隐式和逻辑运算符。我们为函数创建了单独的类来评估函数,因此就像您对变量的想法一样,我创建了一个具有函数的映射并照顾函数名称。每个函数都使用方法eval(T rightOperator和T leftOperator)来实现一个接口,因此任何时候我们都可以添加功能而无需更改算法代码。使其与泛型类型一起使用是一个好主意。谢谢!
您能解释一下该算法背后的逻辑吗?
我试图描述我从Boann编写的代码中了解的内容,并举例说明Wiki。这种算法的逻辑从操作顺序规则开始。 1.操作员标志|变量评估|函数调用|括号(子表达式); 2.求幂3.乘法,除法; 4.加,减;
算法方法按操作顺序的每个级别划分如下:parseFactor = 1。变量评估|函数调用|括号(子表达式); 2.求幂parseTerms = 3.乘法,除法; parseExpression =4。加,减。该算法以相反的顺序调用方法(parseExpression-> parseTerms-> parseFactor-> parseExpression(用于子表达式)),但是第一行的每个方法都将方法调用到下一级,因此整个执行顺序方法将是实际上是正常的操作顺序。
例如,double x = parseTerm();的parseExpression方法在此for (;;) {...}评估实际订单级别的连续操作(加法,减法)之后评估左运算符。和parseTerm方法中的逻辑相同。 parseFactor没有下一个级别,因此仅对方法/变量进行评估,或者在使用括号的情况下-评估子表达式。 boolean eat(int charToEat)方法检查当前光标字符与charToEat字符是否相等,如果相等,则返回true并将光标移至下一个字符,我使用名称接受。
如何修改将能够执行此操作的方法:2(3 + 2)或(50 + 1)6或8(2)9
我试图按照您的步骤操作,以更改算法以接受变量。我将所有返回double的函数转换为return expression,但是我不理解Expression exp = parse("x^2 - x + 2", variables);应该做什么以及如何工作,因为从未声明过解析。同样在parseFactor中,我使用else if (variables.containsKey(func)){x = () -> variables.get(func);}返回我的变量值,但是代码在下一个数字值之后中断了递归。任何帮助表示赞赏。
我问了一个参考您答案的问题-stackoverflow.com/q/40975678/4825796
谢谢您的摘录!基于此,我创建了一个解析器,可以将表达式与=,,!=等进行比较,也可以应用逻辑运算符AND和OR。
@Boann-非常感谢。我将此代码移植到Typescript。
解决此问题的正确方法是使用词法分析器和解析器。您可以自己编写这些的简单版本,或者这些页面还具有指向Java词法分析器和解析器的链接。
创建递归下降解析器是一个非常好的学习练习。
对于我的大学项目,我一直在寻找一种既支持基本公式又支持更复杂方程式(特别是迭代运算符)的解析器/评估器。我发现了非常好的JAVA和.NET开源库,称为mXparser。我将给出一些示例,以使您对语法有一些了解,有关更多说明,请访问项目网站(尤其是教程部分)。
http://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应用程序。
最好的祝福
到目前为止,这是目前最好的数学库。易于启动,易于使用且可扩展。绝对应该是最佳答案。
在这里找到Maven版本。
我发现mXparser无法识别非法公式,例如0/0将得到结果为0。如何解决此问题?
刚刚找到了解决方案expression.setSlientMode()
这里是GitHub上另一个名为EvalEx的开源库。
与JavaScript引擎不同,此库仅专注于评估数学表达式。此外,该库是可扩展的,并支持布尔运算符和括号的使用。
没关系,但是当我们尝试乘以5或10的倍数的值时失败,例如65 * 6导致3.9E + 2 ...
但是有一种方法可以通过将其强制转换为int来解决此问题,即int output =(int)65 * 6现在将导致390
要澄清的是,这不是库的问题,而是数字以浮点值表示的问题。
如果您的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");
向SQL注入打个招呼!
这取决于您使用数据库的目的。如果您想确定的话,可以轻松创建一个空的sqlite DB,专门用于数学评估。
@cyberz如果使用上面的示例,Sqlite将在内存中创建一个临时数据库。见stackoverflow.com/questions/849679/
您也可以尝试BeanShell解释器:
Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));
您能告诉我如何在adnroid Studio中使用BeanShell吗?
Hanni-这篇文章可能会帮助您将BeanShell添加到您的androidstudio项目中:stackoverflow.com/questions/18520875/
本文讨论了各种方法。这是本文中提到的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 namesList = new ArrayList();
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();
}
}
请总结文章中的信息,以防链接断开。
香港专业教育学院升级了答案,以包括文章中的相关内容
另一种方法是使用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
在这里阅读更多简洁的SpEL示例,在这里阅读完整的文档
这是另一个有趣的选择
https://github.com/Shy-Ta/expression-evaluator-demo
用法非常简单,可以完成工作,例如:
ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");
assertEquals(BigDecimal.valueOf(11), evalExpr.eval());
如果我们要实现它,那么我们可以使用以下算法:
尽管仍有令牌可供读取,
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.
当运算符堆栈不为空时,
1从运算符堆栈中弹出运算符。
2弹出两次值堆栈,得到两个操作数。
3以正确的顺序将运算符应用于操作数。
4将结果压入值堆栈。
此时,操作员堆栈应为空,且值
堆栈中应该只有一个值,这是最终结果。
这是Dijkstra调车场算法的未经认可的解释。贷方到期的贷方。
看来JEP应该胜任这项工作
我认为无论采用哪种方式,都会涉及很多条件语句。但是对于示例中的单个操作,如果语句中包含类似以下内容,则可以将其限制为4
String math ="1+4";
if (math.split("+").length == 2) {
//do calculation
} else if (math.split("-").length == 2) {
//do calculation
} ...
当您要处理" 4 + 5 * 6"之类的多种操作时,它变得更加复杂。
如果您要构建一个计算器,那么我会尽量将计算的每个部分(每个数字或运算符)分开传递,而不是作为一个字符串传递。
一旦您必须处理多个操作,运算符优先级,括号等等,实际上就变得更加复杂了,实际上任何具有真实算术表达式特征的事物。从这种技术开始您无法到达那里。
您可以看一下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
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));
}
}
不处理运算符优先级,也不处理多个运算符或括号。不使用。
使用具有代码注入处理功能的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;
}
}
}
这实际上是对@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分组为2^(3^2),因为^是右关联的,但是您所做的修改使它像(2^3)^2那样分组。第二个原因是^应该具有比*和更高的优先级,但是您的修改将其相同。请参阅ideone.com/iN2mMa。
那么,您的建议是将幂运算更好地保留在不是它的地方?
是的,那就是我的建议。
现在回答还为时已晚,但是我遇到了同样的情况,需要用Java评估表达式,这可能会对某人有所帮助
MVEL对表达式进行运行时评估,我们可以在String中编写一个Java代码以对其进行评估。
String expressionStr ="x+y";
Map vars = new HashMap();
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/处理
可以使用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);
}
另一个选择:https://github.com/stefanhaustein/expressionparser
我已经实现了它,以拥有一个简单但灵活的选项来允许这两种方式:
立即处理(Calculator.java,SetDemo.java)
构建和处理解析树(TreeBuilder.java)
上面链接的TreeBuilder是执行符号推导的CAS演示包的一部分。还有一个BASIC解释器示例,我已经开始使用它来构建TypeScript解释器。
这样的事情怎么样:
String st ="10+3";
int result;
for(int i=0;i
{
if(st.charAt(i)=='+')
{
result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
System.out.print(result);
}
}
并相应地对所有其他数学运算符执行类似的操作。
您应该阅读有关编写高效的数学表达式解析器的知识。有一种计算机科学方法论。以ANTLR为例。如果您对所写内容进行了认真的思考,您会发现(a + b / -c)*(e / f)之类的东西将无法实现您的想法,否则代码将变得非常笨拙且效率低下。
这比switch case语句更糟糕...
import java.util.*;
StringTokenizer st;
int ans;
public class check {
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; }
//.........
}
可以使用Djikstra的shunting-yard算法将中缀表示法中的任何表达式字符串转换为后缀表示法。然后,该算法的结果可以用作后缀算法的输入,并返回表达式的结果。
我在这里写了一篇有关Java的实现的文章
一个可以评估数学表达式的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)))"));
}
}
无法正确处理运算符优先级。有标准的方法可以做到这一点,但这不是其中一种。
EJP,能否请您指出运算符优先级存在问题的地方?我完全同意这不是标准方法。在以前的帖子中已经提到了标准方法,其目的是显示另一种方法。