Как использовать java.математика.BigInteger в jjs / Nashorn?


Я хотел бы использовать java.математика.BigInteger в JavaScript nashorn / jss.

В качестве примера предположим, что я хочу вычислить последовательные числа Фибоначчи. Числа должны оставаться точными, даже если они становятся очень большими.

Рабочий код Java выглядит следующим образом:

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

Мы можем проверить с помощью:

  • n=77: 5527939700884757
  • n=78: 8944394323791464
  • n=79: 14472334024676221

Пока все хорошо.

Эквивалент Код JavaScript ниже:

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

Теперь получаем:

  • n=77: 5527939700884757
  • n=78: 8944394323791464
  • n=79: 14472334024676220
Обратите внимание, что значение для 79 равно единице - это неправильно. Я подозреваю, что проблема заключается в том, что где-то значения BigNumber интерпретируются как простые числа JavaScript. (под "где-то" я подозреваю, что это уже происходит, поскольку предположительно BigInteger передается своему .добавить метод)

Например, если я вас сделайте:

var BigInteger = Java.type("java.math.BigInteger");
print(new BigInteger("14472334024676221"));

Выход будет 14472334024676220, а не 14472334024676221. Это происходит, даже если я явно вызываю .toString() на объекте BigInteger.

Как мне пройти через это?

UPDATE: @Dici спросил, ищу ли я порог. Я сделал - я нашел:

var str, BigInteger = Java.type("java.math.BigInteger");
str = "9999999999999998";
print(str + ": " + new BigInteger(str));
str = "9999999999999999";
print(str + ": " + new BigInteger(str));

Выведет:

  • 9999999999999998: 9999999999999998
  • 9999999999999999: 10000000000000000

Я не уверен, что это вопрос "трешхолда" или каких-то конкретных чисел, имеющих неточности хотя.

Обновление 2:

Теперь об этом сообщается как об ошибке: https://bugs.openjdk.java.net/browse/JDK-8146264 Отчет об ошибке был сделан разработчиком Oracle JDK / Nashorn, поэтому я думаю, что это реальная вещь. Держу пальцы скрещенными.

2 3

2 ответа:

Да, это проблема. Ошибка была подана -> https://bugs.openjdk.java.net/browse/JDK-8146264

JSType и несколько других мест имеют проверку "instanceof Number" - не уверен, что исправляет JSType.тострингимпл один справится. В любом случае, у меня есть обходной путь - не очень красивый, но все же обходной путь. Вы можете позвонить на яву.яз..Объект.метод toString на этих объектах, таким образом, избегая кода преобразования строки Jstype Nashorn.

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

Я взял ваш пример:

var BigInteger = Java.type("java.math.BigInteger");
print(new BigInteger("14472334024676221"));

Запустил программу в режиме отладки и заметил, что метод toString BigInteger не используется. Поэтому я создал простой класс:

public class ToString {
    private final BigInteger x;

    public ToString(BigInteger x) {
        this.x = x;
    }

    @Override
    public String toString() {
        return x.toString();
    }
}

И использовал его для вывода BigInteger, и это сработало:

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

Затем я заподозрил, что Нашорн использовал некоторое промежуточное преобразование перед преобразованием BigInteger в String, поэтому я создал точку останова в BigInteger.doubleValue(), и она сработала, когда был напечатан голый BigInteger. Вот проблемная трассировка стека, чтобы вы поняли Логика нэшорна:

  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)

И проблемный код Нэшорна JSType.toStringImpl:

if (obj instanceof Number) {
    return toString(((Number)obj).doubleValue());
}