在Java中评估数学表达式的方法

54
在我的一个项目中,我想添加一个功能,让用户可以提供一个公式,例如:
sin (x + pi)/2 + 1

这是我在我的Java应用程序中使用的

/**
 * The formula provided by the user
 */
private String formula; // = "sin (x + pi)/2 + 1"

/*
 * Evaluates the formula and computes the result by using the
 * given value for x
 */
public double calc(double x) {
    Formula f = new Formula(formula);
    f.setVar("x", x);
    return f.calc();
    // or something similar
}

我该如何评估数学表达式?


我已经用过谷歌搜索,但前20页的所有库都不是开源的,也不是免费的。那么,你的谷歌搜索结果呢? - Ethan Leroy
这是一个广泛的问题。我建议创建一个语法和词法分析器。创建一个AST并通过遍历树来评估表达式。从简单开始,通过修改语法扩大范围。您需要定义什么构成可以评估的有效表达式以及什么不能。算术和三角函数是一回事;贝塞尔和积分函数是另一回事。 - duffymo
8个回答

30

此外还有exp4j,这是一个基于Dijkstra's Shunting Yard的表达式求值器。它可以自由地在Apache许可证2.0下获取和重新分发,大小仅约为25KB,并且非常易于使用:

Calculable calc = new ExpressionBuilder("3 * sin(y) - 2 / (x - 2)")
        .withVariable("x", varX)
        .withVariable("y", varY)
        .build()
double result1=calc.calculate();

当使用更新的API版本,例如0.4.8时:

Expression calc = new ExpressionBuilder("3 * sin(y) - 2 / (x - 2)")
    .variable("x", x)
    .variable("y", y)
    .build();
double result1 = calc.evaluate();

exp4j 中,还有一个使用自定义函数的工具。


它不支持科学计数法:例如1.5e+3*x。 - Hiep
2
exp4j 0.3.5及之后的版本增加了对科学计数法的支持。 - fasseg

14

要扩展此列表,我刚刚完成了其中一个:

https://github.com/uklimaschewski/EvalEx

EvalEx是Java的方便表达式求值器,允许求解简单的数学和布尔表达式。

主要特点:

  • 使用BigDecimal进行计算和结果输出
  • 单个类实现,非常紧凑
  • 不依赖外部库
  • 可以设置精度和舍入模式
  • 支持变量
  • 标准布尔和数学运算符
  • 标准基本数学和布尔函数
  • 可以在运行时添加自定义函数和运算符

示例:

BigDecimal result = null;

Expression expression = new Expression("1+1/3");
result = expression.eval():
expression.setPrecision(2);
result = expression.eval():

result = new Expression("(3.4 + -4.1)/2").eval();

result = new Expression("SQRT(a^2 + b^2").with("a","2.4").and("b","9.253").eval();

BigDecimal a = new BigDecimal("2.4");
BigDecimal b = new BigDecimal("9.235");
result = new Expression("SQRT(a^2 + b^2").with("a",a).and("b",b).eval();

result = new Expression("2.4/PI").setPrecision(128).setRoundingMode(RoundingMode.UP).eval();

result = new Expression("random() > 0.5").eval();

result = new Expression("not(x<7 || sqrt(max(x,9)) <= 3))").with("x","22.9").eval();

它显示cos(rad(90))为0.996 :/ 除此之外很棒。 - harveyslash
1
另一个问题是,我不认为有添加像!(阶乘)这样的运算符的规定。编辑,我从出生开始就被教导cos(0)等于1。别告诉我我的人生是谎言 :O - harveyslash
阶乘可以非常轻松地作为(自定义)函数实现,前缀运算符不受支持。 - Udo Klimaschewski
那么,我需要使用 !(expression) 对吧? - harveyslash
让我们在聊天中继续这个讨论 - harveyslash
显示剩余4条评论

11

这取决于你想要计算的表达式有多复杂,但对于简单的表达式,Java有一个相当不错的JavaScript引擎:

import javax.script.*;
public class EvalScript {
public static void main(String[] args) throws Exception {
    // create a script engine manager
    ScriptEngineManager factory = new ScriptEngineManager();
    // create a JavaScript engine
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    // evaluate JavaScript code from String
    Object obj = engine.eval("1+2");
    System.out.println( obj );
    }
}

这些函数将比我在问题中使用的函数更加复杂。它们将包含三角函数以及指数等等。我认为JS eval()函数无法胜任。 - Ethan Leroy
3
我绝不会喜欢这种技术。它容易受到注入攻击的威胁,几乎所有基于通用eval的技术都存在这种问题。这是一种糟糕的做法。 - John
@JohnO,那你有什么建议吗?我对Web编程真的不是很专业。 - Snicolas
1
@Snicolas - 一般来说,我会使用一个具有严格输入解析的工具,它将限制为仅数学表达式,没有执行任意代码的可能性。 - John
1
@JohnO 你可以将允许进入 eval 字符串的内容列入白名单。我喜欢这个例子,因为它是原生 Java 的。另外,如果不接受来自不受信任的来源的输入,那么它现在就很好。 - Cruncher

3
Nice math parser 包含广泛的数学集合 - mXparser - 请查看以下示例:
示例1:
import org.mariuszgromada.math.mxparser.*;
...
...
Argument x = new Argument("x = pi");
Expression e = new Expression("sin(x + pi)/2 + 1", x);
mXparser.consolePrintln("Res 1: " + e.getExpressionString() + " = " + e.calculate());
x.setArgumentValue(2);
mXparser.consolePrintln("Res 2: " + e.getExpressionString() + " = " + e.calculate());

结果:

[mXparser-v.4.0.0] Res 1: sin(x + pi)/2 + 1 = 1.0
[mXparser-v.4.0.0] Res 2: sin(x + pi)/2 + 1 = 0.545351286587159

例子2:

import org.mariuszgromada.math.mxparser.*;
...
...
Function f = new Function("f(x) = sin(x + pi)/2 + 1");
Expression e = new Expression("f(pi)", f);
mXparser.consolePrintln("Res: " + e.getExpressionString() + " = " + e.calculate());

结果:

[mXparser-v.4.0.0] Res: f(pi) = 1.0

为了更好的理解,请查看mXparser教程mXparser数学集合
最近发现 - 如果您想尝试语法(并查看高级用例),您可以下载由mXparser驱动的标量 计算器 应用程序
此致敬意。

2
将以下英文段落翻译成中文:

在列表中添加另一个选项,我编写了 Jep Java,它作为 sourceforge 上的开源项目 获得了很高的人气。

它支持所有基本的表达式解析任务。但是,如果您想要自定义它,它还添加了很多可扩展性。许多用户赞扬该库特别写得很好且易于使用。请查看 sourceforge 的评论!

这里是一个简单的例子,只有一个变量:

import com.singularsys.jep.Jep;

Jep jep = new Jep();
jep.addVariable("x", 10);
jep.parse("x+1");
Object result = jep.evaluate();
System.out.println("x + 1 = " + result);

这将会打印出 "x + 1 = 11"。您可以更改变量的值并快速重新评估表达式。
后来,我还在Singular Systems website上提供了商业许可证的库。

感谢您的反馈!已添加了一个关于如何使用Jep的示例。 - Nathan Funk
这是一个很棒的库。 - aran

1

我有一个小而功能强大的数学计算器,它完全没有限制。

主要特点

  • 基本数学运算符,具有推断优先级(+ - * × / ÷ % ^)。
  • 使用括号进行明确的运算优先级。
  • 隐式乘法操作。
  • 正确处理幂运算(指数运算符)的右结合性。
  • 直接支持以“0x”为前缀的十六进制数。
  • 常量和变量。
  • 可扩展函数。
  • 可扩展运算符。
  • 仅占用20 KiB的内存空间。

以下是一个简单的示例,可以计算文本显示子部分的中间列(偏向左侧)。

MathEval            math=new MathEval();

math.setVariable("Top",    5);
math.setVariable("Left",  20);
math.setVariable("Bottom",15);
math.setVariable("Right", 60);

System.out.println("Middle: "+math.evaluate("floor((Right+1-Left)/2)"));                        // 20

我已经快速浏览了你的网站/代码,总体看起来很有趣。你有任何单元测试或打包的JAR文件吗?我可以轻松地将你的Java源文件导入我的代码,但如果有Maven构件的话,我更喜欢使用它。 - Eric B.
不,我故意避免发布除原始源代码以外的任何复杂内容。我确实有一个测试类,但不会发布它。 - Lawrence Dol
1
为什么不把测试类也发布出来呢?这样不仅可以帮助理解MathEval类的工作原理,同时也能提供确保一切按预期运行的保障。 - Eric B.
这个支持虚数吗? - Henry Zhu
@Henry:不是的。但扩展功能可能可用于添加一元运算符“i”,以便123i可以按预期工作。然而,我不知道足够确定它是否可行。并且命名变量存在潜在的冲突,尽管可以使用其他符号来表示“虚数”来缓解这种冲突。但对我来说似乎高度不可能。 - Lawrence Dol

0

我之前已经在这里发布了类似的答案。我只是想说,我一直在开发一个小型库,支持数学、布尔和字符串表达式的计算。这里是一个小例子:

String expression = "EXP(var)";
ExpressionEvaluator evaluator = new ExpressionEvaluator();
evaluator.putVariable(new Variable("var", VariableType.NUMBER, new BigDecimal(20)));

System.out.println("Value of exp(var) : " + evaluator.evaluate(expression).getValue());

如果您感兴趣,可以在这里找到。


0
您可以查看 ParserNG,一个完整且极快的数学表达式解析器,它在编译-求值阶段工作。
编译阶段解析/理解表达式。求值阶段解决它,并且速度非常快。
我称它为解析器,但它不仅仅是解析器。
它评估数学表达式,支持变量和常量的创建和使用,支持内置和自定义函数的创建,解决二次方程,同时方程, Tartaglia's 方程(a.x^3+b.x+c=0),还使用迭代方式通过多种后备技术解决单变量方程,进行基本统计,微分计算(包括其自己实现的符号微分器,它用于在给定值处输出导数的数值),解决数值积分。
它还通过 Function 类处理矩阵,并使用矩阵函数支持各种矩阵操作。
这甚至还不是全部。
例如,以下是解析器执行一些微分计算的示例:
MathExpression expr = new MathExpression("f(x)=x^3*ln(x); diff(f,3,1)"); 
System.out.println("result: " + expr.solve());

 result: 38.66253179403897

这个stackoverflow答案中查看更多相关信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接