Reputation: 31961
I would like to use java.math.BigInteger in a nashorn / jss JavaScript.
By way of example, let's say I want to calculate Fibonacci sequence numbers. Numbers will need to remain exact, even if they become very large.
Working Java code looks like this:
public static BigInteger fibonacci(int n) {
BigInteger prev = new BigInteger("0");
if (n == 0) return prev;
BigInteger next = new BigInteger("1");
if (n == 1) return next;
BigInteger fib = null;
int i;
for (i = 1; i < n; i++) {
fib = prev.add(next);
prev = next;
next = fib;
}
return fib;
}
We can test with:
So far so good.
Equivalent JavaScript code below:
function fibonacci(n) {
var BigInteger = Java.type("java.math.BigInteger");
prev = new BigInteger("0");
if (n == 0) return prev;
next = new BigInteger("1");
if (n == 1) return next;
var i, fib = null;
for (i = 1; i < n; i++) {
fib = prev.add(next);
prev = next;
next = fib;
}
return fib;
}
Now we get:
Note that the value for 79 is one off - it's wrong.
I suspect the problem is that somewhere, the BigNumber values are re-interpreted as plain JavaScript Numbers. (by "somewhere" I suspect this already happens as the supposedly BigInteger is passed to the .add method)
For example, if I you do:
var BigInteger = Java.type("java.math.BigInteger");
print(new BigInteger("14472334024676221"));
The output is 14472334024676220
, not 14472334024676221
.
This happens even if I explicitly call .toString()
on the BigInteger object.
How do I get past this?
UPDATE: @Dici asked if I looked for a threshold. I did - I found:
var str, BigInteger = Java.type("java.math.BigInteger");
str = "9999999999999998";
print(str + ": " + new BigInteger(str));
str = "9999999999999999";
print(str + ": " + new BigInteger(str));
will output:
I'm not sure it it's a matter of "treshold", or of some particular numbers having inaccuracies though.
UPDATE 2:
This is now reported as a bug: https://bugs.openjdk.java.net/browse/JDK-8146264 Bug report was done by a Oracle JDK/Nashorn developer so I guess it's the real thing. Keeping my fingers crossed.
Upvotes: 3
Views: 990
Reputation: 4405
Yes, this is an issue. A bug has been filed -> https://bugs.openjdk.java.net/browse/JDK-8146264
JSType and few other places have "instanceof Number" check -- not sure if fixing JSType.toStringImpl alone will do. In any case, I've a workaround - not very pretty one - but a workaround nevertheless. You can call java.lang.Object.toString method on those objects thereby avoiding Nashorn's JSType string conversion code.
function fibonacci(n) {
var BigInteger = Java.type("java.math.BigInteger");
prev = new BigInteger("0");
if (n == 0) return prev;
next = new BigInteger("1");
if (n == 1) return next;
var i, fib = null;
for (i = 1; i < n; i++) {
fib = prev.add(next);
prev = next;
next = fib;
}
return fib;
}
function javaToString(obj) {
var javaToStringMethod = (new java.lang.Object()).toString;
var call = Function.prototype.call;
return call.call(javaToStringMethod, obj);
}
print(javaToString(fibonacci(77)))
print(javaToString(fibonacci(78)))
print(javaToString(fibonacci(79)))
var str, BigInteger = Java.type("java.math.BigInteger");
str = "9999999999999998";
print(str + ": " + javaToString(new BigInteger(str)));
str = "9999999999999999";
print(str + ": " + javaToString(new BigInteger(str)));
Upvotes: 2
Reputation: 3474
I took your example:
var BigInteger = Java.type("java.math.BigInteger");
print(new BigInteger("14472334024676221"));
Started the program in debug mode and noticed that toString
method of BigInteger
was not used. So I created a simple class:
public class ToString {
private final BigInteger x;
public ToString(BigInteger x) {
this.x = x;
}
@Override
public String toString() {
return x.toString();
}
}
And used it in order to output the BigInteger
, and it worked:
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine jsEngine = scriptEngineManager.getEngineFactories().get(0).getScriptEngine();
String script = "var BigInteger = Java.type(\"java.math.BigInteger\");\n" +
"var ToString = Java.type(\"com.stackoverflow.inner.ToString\");\n" +
"var ts = new ToString(new BigInteger(\"14472334024676221\"));\n" +
"print(ts);";
jsEngine.eval(script); // prints 14472334024676221
Then I suspected that Nashorn used some intermediate conversion before converting BigInteger
to String
so I created a breakpoint at BigInteger.doubleValue()
and it triggered when bare BigInteger
was printed. Here is the problematic stack trace to let you understand Nashorn's logic:
at java.math.BigInteger.doubleValue(BigInteger.java:3888)
at jdk.nashorn.internal.runtime.JSType.toStringImpl(JSType.java:976)
at jdk.nashorn.internal.runtime.JSType.toString(JSType.java:327)
at jdk.nashorn.internal.runtime.JSType.toCharSequence(JSType.java:341)
at jdk.nashorn.internal.objects.NativeString.constructor(NativeString.java:1140)
And the problematic Nashorn's code JSType.toStringImpl
:
if (obj instanceof Number) {
return toString(((Number)obj).doubleValue());
}
Upvotes: 2