Арифметические расчеты Jexl неточны

У меня есть приложение, которое получает список выражений JEXL и обрабатывает их в цикле с данными, полученными из базы данных. Это работает, но расчеты не точны. Например, эта простая формула:

a*b+c

где a — целое число, b — число с плавающей запятой и c — двойное число. со значениями a = 600, b = 6,287 и c = 102,245 результат должен быть 3874,445, но JEXL дает мне 3874,223. Я проверил это на нескольких сотнях образцов, погрешность варьируется от 0,001% до 0,3%, но точного ответа я так и не получил. Мой движок JEXL выглядит так:

JexlEngine jexl = new JexlBuilder().cache(2048).silent(false).strict(false).create();

Я установил для параметра strict значение false, потому что некоторые данные могут быть нулевыми, и я не хочу, чтобы JEXL блевал на такие данные. Есть идеи, почему это происходит?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короткий ответ: посмотрите на двоичный формат с плавающей запятой двойной точности IEEE 754. Это означает, что даже в чистом Java-коде вы получите такой результат:

double r = 600 * 6.287f + 102.245d; // r == 3874.4451953125

Более длинный ответ заключается в том, что вы можете получить JexlArithmetic, чтобы каждый оператор преобразовывал свои аргументы в BigDecimal для получения точного ответа. В качестве примера следующее оценивается «правильно» (обратите внимание на 600b, которое делает первое число BigDecimal).

  @Test
  public void testSO20230225() throws Exception {
    String src = "let a = 600b; let b = 6.287f; let c = 102.245d; a * b + c";
    final JexlBuilder builder = new JexlBuilder();
    final JexlEngine jexl = builder.create();
    JexlScript script = jexl.createScript(src);
    Object result = script.execute(null);
    double r = 3874.445d;
    Assert.assertEquals(r, ((Number) result).doubleValue(), 0.0000001d);
  }

К сожалению, я не могу воспользоваться этим предложением. Мое приложение получает формулу и SQL-запрос к базе данных. Я не знаю, какой будет тип столбца и формула. Это может быть объединение двух значений varchar или чего-то еще. Я получаю значения из БД как List<Map<String,Object>, а затем перебираю этот список, предоставляя параметры в формулу. Есть ли способ заставить JEXL автоматически преобразовывать числовые данные с необходимой точностью?

Gary Greenberg 26.02.2024 17:31

Наверное, я недостаточно ясно выразился, и мне очень жаль, но я не понимаю, почему это предложение не применимо. Опять же, вы можете создать свой собственный JexlArithmetic - чтобы закодировать ожидаемое поведение - и переопределить все операторы (add, multi, div...), чтобы преобразовать все числа в BigDecimal для достижения максимальной точности. Другое решение — выполнить это преобразование (преобразование любого числа в BigDecimal) при итерации по вашим картам, делегируя все, кроме метода get(), который преобразует любое число в BigDecimal.

henrib 26.02.2024 18:00

Еще до вашего комментария я пытался, итерируя по списку, преобразовать if (o instanceof Number) o = ((Number)o).doubleValue(): результат также был неточным. Однако я не пробовал BigDecimal. Попробую сегодня позже.

Gary Greenberg 26.02.2024 21:32

BigDecimal JEXL обеспечивает приемлемую точность, но это большая головная боль.

Gary Greenberg 29.02.2024 05:51

Другие вопросы по теме