У меня есть приложение, которое получает список выражений 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 блевал на такие данные. Есть идеи, почему это происходит?




Короткий ответ: посмотрите на двоичный формат с плавающей запятой двойной точности 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);
}
Наверное, я недостаточно ясно выразился, и мне очень жаль, но я не понимаю, почему это предложение не применимо. Опять же, вы можете создать свой собственный JexlArithmetic - чтобы закодировать ожидаемое поведение - и переопределить все операторы (add, multi, div...), чтобы преобразовать все числа в BigDecimal для достижения максимальной точности. Другое решение — выполнить это преобразование (преобразование любого числа в BigDecimal) при итерации по вашим картам, делегируя все, кроме метода get(), который преобразует любое число в BigDecimal.
Еще до вашего комментария я пытался, итерируя по списку, преобразовать if (o instanceof Number) o = ((Number)o).doubleValue(): результат также был неточным. Однако я не пробовал BigDecimal. Попробую сегодня позже.
BigDecimal JEXL обеспечивает приемлемую точность, но это большая головная боль.
К сожалению, я не могу воспользоваться этим предложением. Мое приложение получает формулу и SQL-запрос к базе данных. Я не знаю, какой будет тип столбца и формула. Это может быть объединение двух значений varchar или чего-то еще. Я получаю значения из БД как List<Map<String,Object>, а затем перебираю этот список, предоставляя параметры в формулу. Есть ли способ заставить JEXL автоматически преобразовывать числовые данные с необходимой точностью?