2

I am trying to make a method that takes a string formula, and solves the integral of that formula by doing a Riemann's sum with very small intervals. I am using the ScriptEngine and ScriptEngineManager classes to evaluate the function (with the eval() method). For some reason, I am getting this error:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double at sum.integral(sum.java:31) at 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;
    }

}
David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • It may be worth noting that `for (double i = lower; i < upper; i += .001)` is going to accumulate errors since `0.001` is not exactly representable in floating point. [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) (This may or may not affect the accuracy of your results; I'm not sure.) – David Conrad May 30 '17 at 00:30
  • I cannot for the life of me imagine why anyone would downvote this question. Erik provided his code, the problem he was having, and the precise error message he was receiving. What more could SO users possibly ask for in a question? – David Conrad May 30 '17 at 00:41

2 Answers2

3

Nashorn uses optimistic typing (since JDK 8u40), so it will using integers when doubles are not needed. Thus, you cannot count on it returning a Double.

Also, 5*x^2 means "five times x xor two" in JavaScript. The ** exponentiation operator is defined in newer versions of the JavaScript language, but Nashorn doesn't support it yet.

If you change your JavaScript code to 5*x*x it will work, but it would be safer to do:

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

Compiling Frequently Used Code

Since you call this function repeatedly in a loop, a best practice is to compile the function in advance. This performance optimization is not strictly necessary, but as it is the engine has to compile your function every time (although it may use a cache to help with that).

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

Using ES6 Language Features

In the future, when Nashorn does support the ** operator, if you want to use it you may need to turn on ES6 features like this:

import jdk.nashorn.api.scripting.NashornScriptEngineFactory;

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

Or like this:

java -Dnashorn.args=--language=es6

* Edited to account for the mathematical fix pointed out in the comments.

David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • So this solution also worked well, but I am encountering another problem. There seems to be a very large rounding error due to Java, as when I evaluate this I get 205.867 instead of 208 and 1/3, which is the real solution. Is there anyway around this? – LemurLangdon123 May 29 '17 at 23:56
  • It's not rounding error but math error :) See my updated answer for a fix on that part. – Hugues M. May 30 '17 at 00:16
2

Your JS snippet returns an Integer (*), because x^2 is not the correct way to get a power of 2 in JavaScript. Try 5*Math.pow(x,2) instead, and the expression will return a Double.

In JavaScript, ^ operator is bitwise XOR.

Also the loop to compute the integral is wrong, you need to multiply by rectangle width:

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

(*) See David's answer for a tentative explanation. But in comments, @A.Sundararajan provides evidence against this. I have not investigated the exact reason, I have only observed I got an Integer, and was only guessing the use of bitwise operation in expression (from OP's original code) was triggering a conversion to integer. I originally edited my post to include the fix for "math error", but David's newer answer (by about 4 minutes ^^) is more complete for the original question, and should remain the accepted answer IMHO.

Hugues M.
  • 19,846
  • 6
  • 37
  • 65
  • So this seems to have worked, but I'm getting a significant rounding error or something. I also multiplied total by the rectangle length of .001. – LemurLangdon123 May 29 '17 at 23:51
  • Yes the code was missing this multiplication by delta (rectangle width), but with this I get expected error. For example if I simplify expression to `x*x` on [0,1] interval, exact result is `1/3`, and I get `0.33283350000000095` for delta=`0.001`, and `0.3333833349999431 ` for delta = `0.0001`. That looks "alright" to me. – Hugues M. May 30 '17 at 00:13
  • @Hugues Moreau - that is perfect, thanks! Another question - is evaluating trigonometric and exponential / logarithmic functions possible with this same idea? – LemurLangdon123 May 30 '17 at 00:23
  • Small correction: optimistic types defaults to true only in jdk 9. In jdk 8u, optimistic types defaults to false. That said, please do expect Java Number type values for JS numbers as mentioned by David Conrad. – A. Sundararajan May 30 '17 at 02:55
  • About optimistic typing, David's reference says it appeared in 8u40 and looks legitimate. And I did observe `Integer` result with 8u131. There could have been another reason... But I don't see what. (And about the math thing I was just pointing that the error margin looked reasonable in that case. Properly measuring accuracy of rectangle method is not as simple as "about the size of delta". High frequency trigonometric function like `sin(1000x)` would not fit so well for example) – Hugues M. May 30 '17 at 05:27
  • Please refer to http://hg.openjdk.java.net/jdk8u/jdk8u/nashorn/file/cdb7d9454d25/src/jdk/nashorn/internal/runtime/resources/Options.properties and http://hg.openjdk.java.net/jdk9/dev/nashorn/file/c8d6b740f0f7/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Options.properties – A. Sundararajan May 30 '17 at 07:52
  • 2
    As can be seen from above source references, optimistic typing is *available* [ from jdk 8u40 ]. But, the default of that command line flag was set to false (and is still false) in jdk 8u. In jdk 9, optimistic types default is true. – A. Sundararajan May 30 '17 at 08:00
  • @A.Sundararajan Thanks, very interesting, updated my answer. In addition to this strong evidence, I see from your profile you must know what you're talking about :) – Hugues M. May 30 '17 at 08:37