Как оценить математическое выражение в виде строки?


318

Я пытаюсь написать подпрограмму Java для оценки простых математических выражений из Stringзначений, таких как:

  1. "5+3"
  2. "10-40"
  3. "10*3"

Я хочу избегать множества утверждений if-then-else. Как я могу это сделать?


7
Недавно я написал анализатор математических выражений exp4j, выпущенный под лицензией apache, вы можете проверить его здесь: objecthunter.net/exp4j
fasseg

2
Какие выражения вы разрешаете? Только выражения одного оператора? Разрешены ли скобки?
Raedwald

3

1

3
Как это можно считать слишком широким? Оценка Дейкстры является очевидным решением здесь en.wikipedia.org/wiki/Shunting-yard_algorithm
Мартин Spamer

Ответы:


376

С JDK1.6 вы можете использовать встроенный движок Javascript.

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Test {
  public static void main(String[] args) throws ScriptException {
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    String foo = "40+2";
    System.out.println(engine.eval(foo));
    } 
}

52
Кажется, там есть большая проблема; Он выполняет скрипт, а не оценивает выражение. Чтобы было ясно, engine.eval ("8; 40 + 2"), выводит 42! Если вам нужен синтаксический анализатор выражений, который также проверяет синтаксис, я только что закончил (потому что не нашел ничего, что соответствовало бы моим потребностям): Javaluator .
Жан-Марк Астесана

4
В качестве примечания: если вам нужно использовать результат этого выражения в другом месте вашего кода, вы можете типизировать результат в Double, как return (Double) engine.eval(foo);
показано ниже

38
Примечание по безопасности: Никогда не используйте это в контексте сервера с пользовательским вводом. Выполненный JavaScript может получить доступ ко всем классам Java и, таким образом, захватить ваше приложение без ограничений.
Boann

3
@Boann, я прошу вас дать мне ссылку на то, что вы сказали. (Чтобы быть уверенным на 100%)
partho

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- записывает файл через JavaScript в (по умолчанию) текущую директорию программы
Boann

236

Я написал этот evalметод для арифметических выражений, чтобы ответить на этот вопрос. Это делает сложение, вычитание, умножение, деление, возведение в степень (используя ^символ), и несколько основных функций, как sqrt. Он поддерживает группировку с использованием (... ), и он корректно определяет правила приоритета операторов и ассоциативности .

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation

            return x;
        }
    }.parse();
}

Пример:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

Выход: 7,5 (что правильно)


Анализатор является анализатором рекурсивного спуска , поэтому внутренне использует отдельные методы синтаксического анализа для каждого уровня приоритета оператора в своей грамматике. Я держал его коротким , так что легко изменить, но вот некоторые идеи , которые вы могли бы хотеть , чтобы расширить его:

  • Переменные:

    Бит синтаксического анализатора, который читает имена для функций, можно легко изменить для обработки пользовательских переменных, просматривая имена в таблице переменных, передаваемой evalметоду, например a Map<String,Double> variables.

  • Отдельная компиляция и оценка:

    Что если, добавив поддержку переменных, вы захотите вычислять одно и то же выражение миллионы раз с измененными переменными, не анализируя его каждый раз? Это возможно. Сначала определите интерфейс, который будет использоваться для оценки предварительно скомпилированного выражения:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Теперь измените все методы, которые возвращают doubles, поэтому вместо этого они возвращают экземпляр этого интерфейса. Лямбда-синтаксис Java 8 отлично подходит для этого. Пример одного из измененных методов:

    Expression parseExpression() {
        Expression x = parseTerm();
        for (;;) {
            if (eat('+')) { // addition
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() + b.eval());
            } else if (eat('-')) { // subtraction
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() - b.eval());
            } else {
                return x;
            }
        }
    }

    Это создает рекурсивное дерево Expressionобъектов, представляющих скомпилированное выражение ( абстрактное синтаксическое дерево ). Затем вы можете скомпилировать его один раз и повторно оценить с различными значениями:

    public static void main(String[] args) {
        Map<String,Double> variables = new HashMap<>();
        Expression exp = parse("x^2 - x + 2", variables);
        for (double x = -20; x <= +20; x++) {
            variables.put("x", x);
            System.out.println(x + " => " + exp.eval());
        }
    }
  • Различные типы данных:

    Вместо этого doubleвы можете изменить оценщик, чтобы использовать что-то более мощное, например BigDecimalкласс, реализующий комплексные числа или рациональные числа (дроби). Вы даже можете использовать Object, позволяя смешивать типы данных в выражениях, как настоящий язык программирования. :)


Весь код в этом ответе опубликован в открытом доступе . Радоваться, веселиться!


1
Хороший алгоритм, исходя из которого мне удалось реализовать и логические операторы. Мы создали отдельные классы для функций, чтобы оценить функцию, поэтому, как и ваша идея переменных, я создаю карту с функциями и присматриваюсь к имени функции. Каждая функция реализует интерфейс с методом eval (T rightOperator, T leftOperator), поэтому в любое время мы можем добавлять функции без изменения кода алгоритма. И это хорошая идея, чтобы заставить его работать с универсальными типами. Спасибо вам!
Василе Борс

1
Можете ли вы объяснить логику этого алгоритма?
iYonatan

1
Я пытаюсь дать описание того, что я понимаю, из кода, написанного Boann, и примеров, описанных вики. Логика этого алгоритма начинается с правил порядка работы. 1. знак оператора | оценка переменных | вызов функции | скобки (подвыражения); 2. возведение в степень; 3. умножение, деление; 4. сложение, вычитание;
Василе Борс

1
Методы алгоритма делятся для каждого уровня порядка операций следующим образом: parseFactor = 1. знак оператора | оценка переменных | вызов функции | скобки (подвыражения); 2. возведение в степень; parseTerms = 3. умножение, деление; parseExpression = 4. сложение, вычитание. Алгоритм, вызов методов в обратном порядке (parseExpression -> parseTerms -> parseFactor -> parseExpression (для подвыражений)), но каждый метод в первой строке вызывает метод на следующий уровень, поэтому все методы порядка выполнения будут на самом деле нормальный порядок операций.
Василе Борс

1
Например, метод parseExpression double x = parseTerm(); оценивает левый оператор, после этого for (;;) {...}оценивает последовательные операции фактического уровня заказа (сложение, вычитание). Та же логика есть и в методе parseTerm. ParseFactor не имеет следующего уровня, поэтому существуют только оценки методов / переменных или, в случае парантеза, - оценка подвыражения. boolean eat(int charToEat)Проверка метода равенство текущего символа курсора с характером charToEat, если равно возвращение истинный и перемещает курсор к следующему символу, я использую имя «принять» для него.
Василий Борс

34

Правильный способ решить это с помощью лексера и парсера . Вы можете написать их простые версии, или на этих страницах также есть ссылки на лексеры и парсеры Java.

Создание парсера с рекурсивным спуском - действительно хорошее упражнение в обучении.


26

Для моего университетского проекта я искал парсер / оценщик, поддерживающий как базовые формулы, так и более сложные уравнения (особенно итерированные операторы). Я нашел очень хорошую библиотеку с открытым исходным кодом для JAVA и .NET, которая называется mXparser. Я приведу несколько примеров, чтобы немного разобраться в синтаксисе, для дальнейших инструкций, пожалуйста, посетите веб-сайт проекта (особенно раздел учебника).

https://mathparser.org/

https://mathparser.org/mxparser-tutorial/

https://mathparser.org/api/

И несколько примеров

1 - Простая фурмула

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()

2 - Определяемые пользователем аргументы и константы

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()

3 - Пользовательские функции

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()

4 - Итерация

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()

Найден недавно - если вы хотите попробовать синтаксис (и посмотреть расширенный вариант использования), вы можете скачать приложение Scalar Calculator , которое работает на mXparser.

С уважением


Пока что это лучшая математическая библиотека из всех; простой в использовании, простой в использовании и расширяемый. Определенно должен быть топ ответ.
Трюнкевич Мариуш

Найти Maven версию здесь .
izogfif

Я обнаружил, что mXparser не может определить недопустимую формулу, например, «0/0» получит результат как «0». Как я могу решить эту проблему?
lulijun

Только что нашел решение, expression.setSlientMode ()
lulijun

20

ЗДЕСЬ есть еще одна библиотека с открытым исходным кодом на GitHub под названием EvalEx.

В отличие от движка JavaScript эта библиотека ориентирована только на оценку математических выражений. Кроме того, библиотека расширяема и поддерживает использование логических операторов, а также скобок.


Это нормально, но терпит неудачу, когда мы пытаемся умножить значения, кратные 5 или 10, например, 65 * 6 приводит к 3.9E + 2 ...
paarth batra

.Но есть способ исправить это, приведя его к int, то есть к int output = (int) 65 * 6, теперь это приведет к 390
paarth batra

1
Для пояснения, это не проблема библиотеки, а проблема с представлением чисел в виде значений с плавающей запятой.
DavidBittner

Эта библиотека действительно хороша. @paarth batra Приведение к int удалит все десятичные точки. Используйте это вместо: expression.eval (). ToPlainString ();
einUsername

15

Вы также можете попробовать интерпретатор BeanShell :

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));

1
Подскажите, пожалуйста, как использовать BeanShell в Adnroid Studio.
Ханни

1
Ханни - этот пост может помочь вам добавить BeanShell в ваш проект androidstudio: stackoverflow.com/questions/18520875/…
marciowerner

14

Вы можете легко оценить выражения, если ваше Java-приложение уже обращается к базе данных, без использования других JAR-файлов.

Некоторые базы данных требуют, чтобы вы использовали фиктивную таблицу (например, «двойную» таблицу Oracle), а другие позволят вам оценивать выражения, не «выбирая» из какой-либо таблицы.

Например, в Sql Server или Sqlite

select (((12.10 +12.0))/ 233.0) amount

и в Oracle

select (((12.10 +12.0))/ 233.0) amount from dual;

Преимущество использования БД в том, что вы можете вычислять много выражений одновременно. Кроме того, большинство БД позволит вам использовать очень сложные выражения, а также будет иметь ряд дополнительных функций, которые можно вызывать по мере необходимости.

Однако производительность может снизиться, если многие отдельные выражения необходимо оценивать по отдельности, особенно если БД находится на сетевом сервере.

Следующее в некоторой степени решает проблему производительности, используя базу данных Sqlite в памяти.

Вот полный рабочий пример на Java

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();

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

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");

5
Скажи привет SQL инъекции!
cyberz

Это зависит от того, для чего вы используете БД. Если вы хотите быть уверены, вы можете легко создать пустую базу данных sqlite, специально для оценки математики.
DAB

4
@cyberz Если вы используете мой пример выше, Sqlite создаст временную БД в памяти. См. Stackoverflow.com/questions/849679/…
DAB

11

В этой статье рассматриваются различные подходы. Вот 2 ключевых подхода, упомянутых в статье:

JEXL от Apache

Позволяет для сценариев, которые включают ссылки на объекты Java.

// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );
 
// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );
 
// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);

Используйте движок JavaScript, встроенный в JDK:

private static void jsEvalWithVariable()
{
    List<String> namesList = new ArrayList<String>();
    namesList.add("Jill");
    namesList.add("Bob");
    namesList.add("Laureen");
    namesList.add("Ed");
 
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
 
    jsEngine.put("namesListKey", namesList);
    System.out.println("Executing in script environment...");
    try
    {
      jsEngine.eval("var x;" +
                    "var names = namesListKey.toArray();" +
                    "for(x in names) {" +
                    "  println(names[x]);" +
                    "}" +
                    "namesListKey.add(\"Dana\");");
    }
    catch (ScriptException ex)
    {
        ex.printStackTrace();
    }
}

4
Пожалуйста, суммируйте информацию из статьи, если ссылка на нее не работает.
DJClayworth

Я обновил ответ, включив в него соответствующие фрагменты из статьи
Брэд Паркс

1
на практике JEXL работает медленно (использует интроспекцию bean-компонентов), имеет проблемы с производительностью при многопоточности (глобальный кеш)
Nishi

Полезно знать @Nishi! - Мой вариант использования был для отладки вещей в реальных средах, но не являлся частью обычного развернутого приложения.
Брэд Паркс

10

Другой способ - использовать Spring Expression Language или SpEL, который делает намного больше вместе с оценкой математических выражений, поэтому может быть немного излишним. Вам не нужно использовать Spring Framework, чтобы использовать эту библиотеку выражений, поскольку она автономна. Копирование примеров из документации SpEL:

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0

Прочитайте более краткие примеры SpEL здесь и полные документы здесь


8

если мы собираемся реализовать это, то мы можем использовать следующий алгоритм:

  1. Пока еще есть токены для чтения,

    1.1 Получить следующий токен 1.2 Если токен:

    1.2.1 Число: поместите его в стек значений.

    1.2.2 Переменная: получите ее значение и поместите в стек значений.

    1.2.3 Левая скобка: вставьте ее в стек оператора.

    1.2.4 Правая скобка:

     1 While the thing on top of the operator stack is not a 
       left parenthesis,
         1 Pop the operator from the operator stack.
         2 Pop the value stack twice, getting two operands.
         3 Apply the operator to the operands, in the correct order.
         4 Push the result onto the value stack.
     2 Pop the left parenthesis from the operator stack, and discard it.

    1.2.5 Оператор (назовите это thisOp):

     1 While the operator stack is not empty, and the top thing on the
       operator stack has the same or greater precedence as thisOp,
       1 Pop the operator from the operator stack.
       2 Pop the value stack twice, getting two operands.
       3 Apply the operator to the operands, in the correct order.
       4 Push the result onto the value stack.
     2 Push thisOp onto the operator stack.
  2. Пока стек операторов не пуст, 1 вытолкните оператор из стека операторов. 2 Дважды вытолкните стек значений, получив два операнда. 3 Примените оператор к операндам в правильном порядке. 4 Вставьте результат в стек значений.

  3. На этом этапе стек операторов должен быть пустым, а в стеке значений должно быть только одно значение, что является конечным результатом.


3
Это некредитованная экспозиция алгоритма Dijkstra Shunting-ярд . Кредит, где кредит должен.
Маркиз Лорн



4

Я думаю, что когда бы вы ни делали это, это будет включать в себя много условных утверждений. Но для отдельных операций, как в ваших примерах, вы можете ограничить его до 4, если операторы с чем-то вроде

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

Это становится намного сложнее, когда вы хотите иметь дело с несколькими операциями, такими как «4 + 5 * 6».

Если вы пытаетесь построить калькулятор, я бы предпочел передавать каждую часть вычисления отдельно (каждый номер или оператор), а не как одну строку.


2
Это становится намного сложнее, как только вам приходится иметь дело с несколькими операциями, приоритетами операторов, круглыми скобками, ... фактически всем, что характеризует реальное арифметическое выражение. Вы не можете получить там, начиная с этой техники.
маркиз Лорн

4

Уже слишком поздно, чтобы ответить, но я столкнулся с той же ситуацией, чтобы оценить выражение в Java, это может помочь кому-то

MVELВыполняет оценку выражений во время выполнения, мы можем написать Java-код, Stringчтобы оценить его в этом.

    String expressionStr = "x+y";
    Map<String, Object> vars = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);

Я рыскал и нашел некоторые дополнительные Арифметические функции также обрабатываемые здесь github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/...
thecodefather

Awsome! Это спасло мой день. Спасибо
Sarika.S

4

Вы можете взглянуть на платформу Symja :

ExprEvaluator util = new ExprEvaluator(); 
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30" 

Обратите внимание, что определенно более сложные выражения могут быть оценены:

// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2

4

Попробуйте следующий пример кода, используя Javascript движок JDK1.6 с обработкой внедрения кода.

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class EvalUtil {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static void main(String[] args) {
    try {
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || 5 >3 "));
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || true"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public Object eval(String input) throws Exception{
    try {
        if(input.matches(".*[a-zA-Z;~`#$_{}\\[\\]:\\\\;\"',\\.\\?]+.*")) {
            throw new Exception("Invalid expression : " + input );
        }
        return engine.eval(input);
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
 }
}

4

Это фактически дополняет ответ, данный @Boann. Имеется небольшая ошибка, которая приводит к тому, что «-2 ^ 2» дает ошибочный результат -4.0. Проблемой для этого является тот момент, когда возведение в степень оценивается по-своему. Просто переместите возведение в блок parseTerm (), и все будет в порядке. Посмотрите на приведенный ниже ответ , который @ Boann слегка изменил. Модификация есть в комментариях.

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            //if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4на самом деле нормально, а не ошибка. Это группируется как -(2^2). Попробуйте это на Desmos, например. Ваш код на самом деле вносит несколько ошибок. Во-первых, ^больше не группируются справа налево. Другими словами, 2^3^2предполагается, что сгруппировать как, 2^(3^2)потому что ^это право-ассоциативно, но ваши изменения делают это сгруппировать как (2^3)^2. Второе - это то, что ^должен иметь более высокий приоритет, чем *и /, но ваши модификации относятся к нему одинаково. Смотрите ideone.com/iN2mMa .
Radiodef

Итак, что вы предлагаете, так это то, что возведение в степень лучше хранить там, где оно было, не так ли?
Ромео Сьерра

Да, это то, что я предлагаю.
Radiodef

4
package ExpressionCalculator.expressioncalculator;

import java.text.DecimalFormat;
import java.util.Scanner;

public class ExpressionCalculator {

private static String addSpaces(String exp){

    //Add space padding to operands.
    //https://regex101.com/r/sJ9gM7/73
    exp = exp.replaceAll("(?<=[0-9()])[\\/]", " / ");
    exp = exp.replaceAll("(?<=[0-9()])[\\^]", " ^ ");
    exp = exp.replaceAll("(?<=[0-9()])[\\*]", " * ");
    exp = exp.replaceAll("(?<=[0-9()])[+]", " + "); 
    exp = exp.replaceAll("(?<=[0-9()])[-]", " - ");

    //Keep replacing double spaces with single spaces until your string is properly formatted
    /*while(exp.indexOf("  ") != -1){
        exp = exp.replace("  ", " ");
     }*/
    exp = exp.replaceAll(" {2,}", " ");

       return exp;
}

public static Double evaluate(String expr){

    DecimalFormat df = new DecimalFormat("#.####");

    //Format the expression properly before performing operations
    String expression = addSpaces(expr);

    try {
        //We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and
        //subtraction will be processed in following order
        int indexClose = expression.indexOf(")");
        int indexOpen = -1;
        if (indexClose != -1) {
            String substring = expression.substring(0, indexClose);
            indexOpen = substring.lastIndexOf("(");
            substring = substring.substring(indexOpen + 1).trim();
            if(indexOpen != -1 && indexClose != -1) {
                Double result = evaluate(substring);
                expression = expression.substring(0, indexOpen).trim() + " " + result + " " + expression.substring(indexClose + 1).trim();
                return evaluate(expression.trim());
            }
        }

        String operation = "";
        if(expression.indexOf(" / ") != -1){
            operation = "/";
        }else if(expression.indexOf(" ^ ") != -1){
            operation = "^";
        } else if(expression.indexOf(" * ") != -1){
            operation = "*";
        } else if(expression.indexOf(" + ") != -1){
            operation = "+";
        } else if(expression.indexOf(" - ") != -1){ //Avoid negative numbers
            operation = "-";
        } else{
            return Double.parseDouble(expression);
        }

        int index = expression.indexOf(operation);
        if(index != -1){
            indexOpen = expression.lastIndexOf(" ", index - 2);
            indexOpen = (indexOpen == -1)?0:indexOpen;
            indexClose = expression.indexOf(" ", index + 2);
            indexClose = (indexClose == -1)?expression.length():indexClose;
            if(indexOpen != -1 && indexClose != -1) {
                Double lhs = Double.parseDouble(expression.substring(indexOpen, index));
                Double rhs = Double.parseDouble(expression.substring(index + 2, indexClose));
                Double result = null;
                switch (operation){
                    case "/":
                        //Prevent divide by 0 exception.
                        if(rhs == 0){
                            return null;
                        }
                        result = lhs / rhs;
                        break;
                    case "^":
                        result = Math.pow(lhs, rhs);
                        break;
                    case "*":
                        result = lhs * rhs;
                        break;
                    case "-":
                        result = lhs - rhs;
                        break;
                    case "+":
                        result = lhs + rhs;
                        break;
                    default:
                        break;
                }
                if(indexClose == expression.length()){
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose);
                }else{
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose + 1);
                }
                return Double.valueOf(df.format(evaluate(expression.trim())));
            }
        }
    }catch(Exception exp){
        exp.printStackTrace();
    }
    return 0.0;
}

public static void main(String args[]){

    Scanner scanner = new Scanner(System.in);
    System.out.print("Enter an Mathematical Expression to Evaluate: ");
    String input = scanner.nextLine();
    System.out.println(evaluate(input));
}

}


1
Не обрабатывает приоритет оператора, или несколько операторов, или круглые скобки. Не используй.
Маркиз Лорн

2

Как насчет чего-то вроде этого:

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

и сделайте то же самое для каждого другого математического оператора соответственно ..


9
Вы должны прочитать о написании эффективных синтаксических анализаторов выражений. В этом есть методология информатики. Взгляните на ANTLR, например. Если вы хорошо подумаете о том, что написали, то увидите, что такие вещи, как (a + b / -c) * (e / f), не будут работать с вашей идеей, или код будет очень грязным и неэффективным.
Даниил Нуриев

2

Можно преобразовать любую строку выражения в инфиксной записи в постфиксную запись с помощью алгоритма шунтирования ярда Джикстры . Результат алгоритма может затем служить входом для постфиксного алгоритма. с возвращением результата выражения.

Я написал статью об этом здесь, с реализацией в Java


2

Еще один вариант: https://github.com/stefanhaustein/expressionparser

Я реализовал это, чтобы иметь простой, но гибкий вариант, позволяющий:

TreeBuilder, связанный выше, является частью демонстрационного пакета CAS, который выполняет символическое создание . Существует также пример интерпретатора BASIC, и я начал создавать интерпретатор TypeScript, используя его.


2

Класс Java, который может оценивать математические выражения:

package test;

public class Calculator {

    public static Double calculate(String expression){
        if (expression == null || expression.length() == 0) {
            return null;
        }
        return calc(expression.replace(" ", ""));
    }
    public static Double calc(String expression) {

        if (expression.startsWith("(") && expression.endsWith(")")) {
            return calc(expression.substring(1, expression.length() - 1));
        }
        String[] containerArr = new String[]{expression};
        double leftVal = getNextOperand(containerArr);
        expression = containerArr[0];
        if (expression.length() == 0) {
            return leftVal;
        }
        char operator = expression.charAt(0);
        expression = expression.substring(1);

        while (operator == '*' || operator == '/') {
            containerArr[0] = expression;
            double rightVal = getNextOperand(containerArr);
            expression = containerArr[0];
            if (operator == '*') {
                leftVal = leftVal * rightVal;
            } else {
                leftVal = leftVal / rightVal;
            }
            if (expression.length() > 0) {
                operator = expression.charAt(0);
                expression = expression.substring(1);
            } else {
                return leftVal;
            }
        }
        if (operator == '+') {
            return leftVal + calc(expression);
        } else {
            return leftVal - calc(expression);
        }

    }

    private static double getNextOperand(String[] exp){
        double res;
        if (exp[0].startsWith("(")) {
            int open = 1;
            int i = 1;
            while (open != 0) {
                if (exp[0].charAt(i) == '(') {
                    open++;
                } else if (exp[0].charAt(i) == ')') {
                    open--;
                }
                i++;
            }
            res = calc(exp[0].substring(1, i - 1));
            exp[0] = exp[0].substring(i);
        } else {
            int i = 1;
            if (exp[0].charAt(0) == '-') {
                i++;
            }
            while (exp[0].length() > i && isNumber((int) exp[0].charAt(i))) {
                i++;
            }
            res = Double.parseDouble(exp[0].substring(0, i));
            exp[0] = exp[0].substring(i);
        }
        return res;
    }


    private static boolean isNumber(int c) {
        int zero = (int) '0';
        int nine = (int) '9';
        return (c >= zero && c <= nine) || c =='.';
    }

    public static void main(String[] args) {
        System.out.println(calculate("(((( -6 )))) * 9 * -1"));
        System.out.println(calc("(-5.2+-5*-5*((5/4+2)))"));

    }

}

2
Не правильно обрабатывает приоритет оператора. Есть стандартные способы сделать это, и это не один из них.
Маркиз Лорн

EJP, можете ли вы указать, где есть проблема с приоритетом оператора? Я полностью согласен с тем, что это не стандартный способ сделать это. стандартные способы уже упоминались в предыдущих постах, идея заключалась в том, чтобы показать другой способ сделать это.
Efi G

2

Внешняя библиотека, такая как RHINO или NASHORN, может быть использована для запуска JavaScript. И javascript может оценить простую формулу без разбора строки. Не влияет на производительность, если код написан хорошо. Ниже приведен пример с RHINO -

public class RhinoApp {
    private String simpleAdd = "(12+13+2-2)*2+(12+13+2-2)*2";

public void runJavaScript() {
    Context jsCx = Context.enter();
    Context.getCurrentContext().setOptimizationLevel(-1);
    ScriptableObject scope = jsCx.initStandardObjects();
    Object result = jsCx.evaluateString(scope, simpleAdd , "formula", 0, null);
    Context.exit();
    System.out.println(result);
}

2
import java.util.*;

public class check { 
   int ans;
   String str="7 + 5";
   StringTokenizer st=new StringTokenizer(str);

   int v1=Integer.parseInt(st.nextToken());
   String op=st.nextToken();
   int v2=Integer.parseInt(st.nextToken());

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.