【问题标题】:Why is the eval class giving me a casting error from int to double?为什么 eval 类给我一个从 int 到 double 的转换错误?
【发布时间】:2017-05-29 23:12:02
【问题描述】:

我正在尝试制作一种采用字符串公式的方法,并通过以非常小的间隔进行黎曼和来求解该公式的积分。我正在使用 ScriptEngine 和 ScriptEngineManager 类来评估函数(使用 eval() 方法)。出于某种原因,我收到此错误:

线程“main”java.lang.ClassCastException 中的异常:java.lang.Integer 无法转换为 java.lang.Double 在 sum.integral(sum.java:31) 在 sum.main(sum.java:13)

import java.beans.Expression;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class sum {

    //testing method
    public static void main(String[] args) throws ScriptException {

        double x = integral("5*x^2",0,5);
        System.out.println(x);

    }

    public static double integral(String function, double lower, double upper) throws ScriptException
    {
        double total = 0;

        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");

        //Solves function from upper to lower with a .001 interval, adding it to the total.
        for (double i = lower; i < upper; i+=.001)
        {
            //evaluates the interval
            engine.put("x",i);
            total += (double)engine.eval(function);
        }

        return total;
    }

}

【问题讨论】:

  • 值得注意的是,for (double i = lower; i &lt; upper; i += .001) 会累积错误,因为0.001 不能完全用浮点表示。 What Every Computer Scientist Should Know About Floating-Point Arithmetic(这可能会也可能不会影响您结果的准确性;我不确定。)
  • 我无法想象为什么有人会否决这个问题。 Erik 提供了他的代码、他遇到的问题以及他收到的准确错误消息。 SO 用户还可以在问题中提出什么要求?

标签: java function eval scriptmanager integral


【解决方案1】:

Nashorn 使用optimistic typing(从JDK 8u40 开始),因此当不需要双精度时它将使用整数。因此,您不能指望它返回 Double。

另外,5*x^2 在 JavaScript 中的意思是“五乘以 x 或二”。 ** 求幂运算符是在较新版本的 JavaScript 语言中定义的,但 Nashorn 还不支持它。

如果您将 JavaScript 代码更改为 5*x*x,它会起作用,但这样做会更安全:

total += 0.001 * ((Number)engine.eval(function)).doubleValue();

编译常用代码

由于您在循环中重复调用此函数,因此最好的做法是提前编译该函数。这种性能优化并不是绝对必要的,但因为引擎必须每次都编译你的函数(尽管它可能使用缓存来帮助)。

import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;

CompiledScript compiledScript = ((Compilable)engine)
    .compile("function func(x) { return " + function + "}");
compiledScript.eval(compiledScript.getEngine()
    .getBindings(ScriptContext.ENGINE_SCOPE));

Invocable funcEngine = (Invocable) compiledScript.getEngine();

// . . .

total += 0.001 * ((Number)funcEngine.invokeFunction("func", i)).doubleValue();

使用 ES6 语言特性

将来,当 Nashorn 确实支持 ** 运算符时,如果你想使用它,你可能需要像这样打开 ES6 功能:

import jdk.nashorn.api.scripting.NashornScriptEngineFactory;

NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine enjin = factory.getScriptEngine("--language=es6");

或者像这样:

java -Dnashorn.args=--language=es6

* 已编辑以说明 cmets 中指出的数学修正。

【讨论】:

  • 所以这个解决方案也很有效,但我遇到了另一个问题。由于 Java,似乎存在非常大的舍入误差,因为当我对此进行评估时,我得到 205.867 而不是 208 和 1/3,这是真正的解决方案。反正有这个吗?
  • 不是舍入错误,而是数学错误 :) 请参阅我更新的答案以获取该部分的修复。
【解决方案2】:

您的 JS sn-p 返回 Integer (*),因为 x^2 不是在 JavaScript 中获得 2 次方的正确方法。改用5*Math.pow(x,2),表达式将返回Double

在 JavaScript 中,^ 运算符是 bitwise XOR

另外计算积分的循环是错误的,你需要乘以矩形宽度:

    double delta = 0.001;
    for (double i = lower; i < upper; i += delta) {
        //evaluates the interval
        engine.put("x", i);
        total += delta * ((Number) engine.eval(function)).doubleValue();
    }

(*) 暂定解释见David's answer。但在 cmets 中,@A.Sundararajan 提供了反对这一点的证据。我没有调查确切的原因,我只观察到我得到了一个Integer,并且只是猜测在表达式中使用按位运算(来自 OP 的原始代码)正在触发到整数的转换。我最初编辑了我的帖子以包含“数学错误”的修复,但大卫的新答案(大约 4 分钟 ^^)对于原始问题来说更完整,并且应该仍然是接受的答案恕我直言。

【讨论】:

  • 所以这似乎奏效了,但我遇到了重大的舍入错误或其他问题。我还将总数乘以 0.001 的矩形长度。
  • 是的,代码缺少这个乘以 delta(矩形宽度),但是这样我得到了预期的错误。例如,如果我在 [0,1] 区间上将表达式简化为 x*x,则精确结果是 1/3,我得到 0.33283350000000095 对应 delta=0.0010.3333833349999431 对应 delta = 0.0001。对我来说,这看起来“不错”。
  • @Hugues Moreau - 太好了,谢谢!另一个问题 - 是否可以用同样的想法评估三角函数和指数/对数函数?
  • 小修正:乐观类型仅在 jdk 9 中默认为 true。在 jdk 8u 中,乐观类型默认为 false。也就是说,请期待 David Conrad 提到的 JS 数字的 Java Number 类型值。
  • 从上面的参考资料可以看出,乐观类型是available [from jdk 8u40]。但是,该命令行标志的默认值在 jdk 8u 中设置为 false(并且仍然为 false)。在 jdk 9 中,乐观类型默认为 true。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多