【问题标题】:Method for evaluating math expressions in JavaJava中计算数学表达式的方法
【发布时间】:2011-11-07 16:22:32
【问题描述】:

在我的一个项目中,我想添加一个用户可以在公式中提供的功能,例如

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
}

如何计算数学表达式?

【问题讨论】:

  • 我已经用过 google,但前 20 个结果中的所有库既不是开源也不是免费的。那么,你的谷歌发现了什么?
  • 这是一个广泛的问题。我建议创建一个语法和词法分析器/解析器。创建一个 AST 并通过遍历树来评估表达式。从简单开始,通过修改语法来扩大圈子。您需要定义什么构成可以评估的有效表达式,什么不能。算术和三角函数是一回事;贝塞尔函数和积分函数是另一个。

标签: java math formula


【解决方案1】:

这取决于您要评估的表达式有多复杂,但对于简单的表达式,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() 函数可以做到这一点。
  • 我绝对不会喜欢这种技术。它对注入攻击很开放,就像几乎所有基于通用评估的技术一样。不好的做法。
  • @JohnO 那你有什么建议呢?我真的不是网络编程专家。
  • @Snicolas - 一般来说,我会使用对输入进行严格解析的工具,这会将其限制为仅限数学表达式,不可能执行任意代码。
  • @JohnO 您可以将可以进入 eval 字符串的内容列入白名单。我喜欢这个例子,因为它是原生的 Java。此外,如果您不接受来自不受信任来源的输入,那也没关系
【解决方案2】:

还有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
  • exp4j 0.3.5 以后添加了对科学记数法的支持
【解决方案3】:

我有一个占地面积小但功能强大的math evaluator,完全不受阻碍。

主要特点

  • 具有推断优先级的基本数学运算符 (+-*×/÷%^)。
  • 带括号的显式优先级。
  • 括号内的子表达式的隐式乘法。
  • 修正指数的右结合性(幂运算符)。
  • 直接支持以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

【讨论】:

  • 我快速浏览了您的网站/代码,整体看起来很有趣。你对它和/或打包的 jars 有任何单元测试吗?我可以轻松地将您的 Java src 文件导入到我的代码中,但如果有的话,我会更喜欢一个 maven 工件。
  • 不,我非常刻意避免发布原始源代码以外的任何内容的复杂性。我确实有一个测试类,但不会发布它。
  • 为什么不也发布测试类呢?是否有助于理解 MathEval 类的工作原理,以及提供一切按预期工作的安慰?
  • 这个支持虚数吗?
  • @Henry:不。但是扩展功能可能用于添加一元运算符i,以便123i 可以根据需要运行。但是,我不知道这是否可行。并且与命名变量存在潜在的内在冲突,尽管可以通过使用其他一些符号来表示“虚构”来缓解这种冲突。但这对我来说似乎不太可能。
【解决方案4】:

为了扩展列表,我也刚刚完成了一个:

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 :/ 否则很好
  • 引用另一个问题的答案:BigDecimal 不提供这些方法,因为 BigDecimal 对有理数建模。三角函数、平方根和非整数的幂(我猜包括平方根)都会产生无理数。这些可以用任意精度的数字来近似,但精确值不能存储在 BigDecimal 中。 stackoverflow.com/questions/2173512/…
  • 为什么应该是1?甚至我的桌面计算器也会显示 0,99962421686273250535510392708914 ...
  • 另一个问题,我认为没有规定可以添加像!(阶乘)这样的运算符。编辑,我从出生就被教导 cos(0) 是 1。不要告诉我我的生活是一个谎言:O
  • 阶乘可以很容易地实现为(自定义)函数,不支持前缀运算符。
【解决方案5】:

包含广泛数学集合的漂亮数学解析器 - 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 tutorialmXparser math collection

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

最好的问候

【讨论】:

    【解决方案6】:

    在列表中添加另一个选项,我编写了 Jep Java,它作为 open source project on 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 的示例。
    • 这是一个很棒的图书馆。
    【解决方案7】:

    我已经发布了类似的答案here。我只是想说我一直在研究一个支持数学、布尔和字符串表达式评估的小库。这是一个小例子:

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

    如果你有兴趣,可以here

    【讨论】:

      【解决方案8】:

      您可以查看ParserNG,这是一个成熟且速度极快的数学表达式解析器,可在编译-评估阶段工作。 编译阶段解析/理解表达式。评估阶段解决了它并且非常快。

      我称它为解析器,但它不止于此。

      它评估数学表达式,支持变量和常量的创建和使用,支持内置和创建自定义函数,求解二次方程、联立方程、Tartaglia 方程(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
      

      this stackoverflow answer查看更多信息

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-08
        • 2016-05-26
        • 2014-07-27
        • 2013-03-13
        • 1970-01-01
        • 2015-12-03
        相关资源
        最近更新 更多