Como avaliar uma expressão matemática dada na forma de string?

318

Estou tentando escrever uma rotina Java para avaliar expressões matemáticas simples a partir de Stringvalores como:

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

Eu quero evitar muitas instruções if-then-else. Como posso fazer isso?


fonte
7
Recentemente, escrevi um analisador de expressões matemáticas chamado exp4j, que foi lançado sob a licença apache, você pode conferir aqui: objecthunter.net/exp4j
fasseg
2
Que tipos de expressões você permite? Apenas expressões de operador único? Parênteses são permitidos?
Raedwald
3
Veja também o algoritmo de duas pilhas de Dijkstra
Ritesh
1
Possível duplicata de Existe uma função eval () em Java?
Andrew Li
3
Como isso pode ser considerado muito amplo? Avaliação de Dijkstra é a solução óbvia aqui en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Respostas:

376

Com o JDK1.6, você pode usar o mecanismo Javascript interno.

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));
    } 
}
RealHowTo
fonte
52
Parece que há um grande problema lá; Ele executa um script, não avalia uma expressão. Para ficar claro, engine.eval ("8; 40 + 2") gera 42! Se você deseja um analisador de expressão que também verifique a sintaxe, acabei de terminar um (porque não encontrei nada que atenda às minhas necessidades): Javaluator .
Jean-Marc Astesana
4
Como uma nota lateral, se você precisa usar o resultado desta em outros lugares expressão em seu código, você pode estereotipado o resultado para um casal assim: return (Double) engine.eval(foo);
Ben Visness
38
Nota de segurança: você nunca deve usá-lo em um contexto de servidor com entrada do usuário. O JavaScript executado pode acessar todas as classes Java e, assim, seqüestrar seu aplicativo sem limite.
Boann
3
@Boann, eu pedir-lhe para me dar uma referência sobre o que você disse (para ter certeza de 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();");- gravará um arquivo via JavaScript no diretório atual do programa
Boann
236

Eu escrevi esse evalmétodo para expressões aritméticas para responder a essa pergunta. Faz adição, subtração, multiplicação, divisão, exponenciação (usando o ^símbolo) e algumas funções básicas, como sqrt. Ele suporta o agrupamento usando (... )e corrige as regras de precedência e associatividade do operador .

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

Exemplo:

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

Saída: 7,5 (correta)


O analisador é um analisador de descida recursiva ; portanto, usa internamente métodos de análise separados para cada nível de precedência do operador em sua gramática. Eu o mantive curto, para facilitar a modificação, mas aqui estão algumas idéias que você pode querer expandi-lo:

  • Variáveis:

    O bit do analisador que lê os nomes das funções também pode ser facilmente alterado para manipular variáveis ​​personalizadas, procurando nomes em uma tabela de variáveis ​​passada para o evalmétodo, como a Map<String,Double> variables.

  • Compilação e avaliação separadas:

    E se, tendo adicionado suporte para variáveis, você quisesse avaliar a mesma expressão milhões de vezes com variáveis ​​alteradas, sem analisá-la toda vez? É possível. Primeiro, defina uma interface a ser usada para avaliar a expressão pré-compilada:

    @FunctionalInterface
    interface Expression {
        double eval();
    }
    

    Agora altere todos os métodos que retornam doubles; portanto, eles retornam uma instância dessa interface. A sintaxe lambda do Java 8 funciona muito bem para isso. Exemplo de um dos métodos alterados:

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

    Isso cria uma árvore recursiva de Expressionobjetos que representam a expressão compilada (uma árvore de sintaxe abstrata ). Em seguida, você pode compilá-lo uma vez e avaliá-lo repetidamente com valores diferentes:

    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());
        }
    }
    
  • Tipos de dados diferentes:

    Em vez de double, você pode alterar o avaliador para usar algo mais poderoso como BigDecimal, ou uma classe que implemente números complexos ou números racionais (frações). Você pode até usar Object, permitindo alguma mistura de tipos de dados em expressões, como uma linguagem de programação real. :)


Todo o código nesta resposta foi liberado para o domínio público . Diverta-se!

Boann
fonte
1
Bom algoritmo, a partir dele consegui implementar operadores lógicos e. Criamos classes separadas para funções para avaliar uma função, assim como sua ideia de variáveis, eu crio um mapa com funções e cuidando do nome da função. Toda função implementa uma interface com um método eval (T rightOperator, T leftOperator), para que a qualquer momento possamos adicionar recursos sem alterar o código do algoritmo. E é uma boa ideia fazê-lo funcionar com tipos genéricos. Te agradece!
Vasile Bors
1
Você pode explicar a lógica por trás desse algoritmo?
IYonatan
1
Eu tento dar uma descrição do que entendo do código escrito por Boann e exemplos descritos no wiki. A lógica desse algoritmo a partir de regras de ordens de operação. 1. sinal de operador | avaliação de variáveis ​​| chamada de função | parênteses (sub-expressões); 2. exponenciação; 3. multiplicação, divisão; 4. adição, subtração;
Vasile Bors
1
Os métodos de algoritmo são divididos para cada nível de ordem de operações, da seguinte forma: parseFactor = 1. sinal do operador | avaliação de variáveis ​​| chamada de função | parênteses (sub-expressões); 2. exponenciação; parseTerms = 3. multiplicação, divisão; parseExpression = 4. adição, subtração. O algoritmo chama métodos na ordem inversa (parseExpression -> parseTerms -> parseFactor -> parseExpression (para subexpressões)), mas todos os métodos da primeira linha chamam o método para o próximo nível, para que todos os métodos da ordem de execução sejam ordem realmente normal de operações.
Vasile Bors
1
Por exemplo, o método parseExpression double x = parseTerm(); avalia o operador esquerdo, depois for (;;) {...}avalia as operações sucessivas do nível de pedido real (adição, subtração). A mesma lógica é e no método parseTerm. O parseFactor não possui próximo nível, portanto, existem apenas avaliações de métodos / variáveis ​​ou, no caso de parênteses - avaliar subexpressão. O boolean eat(int charToEat)método verifica a igualdade do caractere atual do cursor com o caractere charToEat, se igual retornar true e mover o cursor para o próximo caractere, eu uso o nome 'accept' para ele.
Vasile Bors
34

A maneira correta de resolver isso é com um lexer e um analisador . Você mesmo pode escrever versões simples delas, ou essas páginas também possuem links para analisadores e analisadores Java.

Criar um analisador de descida recursiva é realmente um bom exercício de aprendizado.

Greg Hewgill
fonte
26

Para o meu projeto universitário, eu estava procurando por um analisador / avaliador que suporta fórmulas básicas e equações mais complicadas (especialmente operadores iterados). Eu achei uma biblioteca de código aberto muito boa para JAVA e .NET chamada mXparser. Vou dar alguns exemplos para entender a sintaxe. Para obter mais instruções, visite o site do projeto (especialmente a seção do tutorial).

https://mathparser.org/

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

https://mathparser.org/api/

E alguns exemplos

1 - Férmula simples

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

2 - Argumentos e constantes definidas pelo usuário

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 - Funções definidas pelo usuário

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 - Iteração

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

Encontrado recentemente - caso queira experimentar a sintaxe (e veja o caso de uso avançado), você pode fazer o download do aplicativo Calculadora Escalar, desenvolvido com mXparser.

Cumprimentos

Leroy Kegan
fonte
Até agora, esta é a melhor biblioteca de matemática existente; Simples de iniciar, fácil de usar e extensível. Definitivamente deve ser a melhor resposta.
Trynkiewicz Mariusz
Encontre a versão do Maven aqui .
Izogfif 12/03/19
Descobri que o mXparser não pode identificar a fórmula ilegal; por exemplo, '0/0' obterá um resultado como '0'. Como posso resolver este problema?
lulijun
Acabei de encontrar a solução, expression.setSlientMode ()
lulijun
20

O HERE é outra biblioteca de código aberto no GitHub chamada EvalEx.

Diferentemente do mecanismo JavaScript, esta biblioteca é focada na avaliação apenas de expressões matemáticas. Além disso, a biblioteca é extensível e suporta o uso de operadores booleanos, bem como parênteses.

Tanvir
fonte
Este é ok, mas falha quando tentamos valores multiplicam de múltiplos de 5 ou 10, por exemplo, 65 * 6 resultados em 3.9E + 2 ...
batra paarth
.Mas existe uma maneira de fixar este, lançando-a int saída ou seja int = (int) 65 * 6 que irá resultar agora 390
Batra paarth
1
Para esclarecer, isso não é um problema da biblioteca, mas um problema com a representação de números como valores de ponto flutuante.
DavidBittner
Esta biblioteca é realmente boa. @paarth batra A transmissão para int removerá todos os pontos decimais. Use isso: expression.eval (). ToPlainString ();
einUsername 6/03
15

Você também pode tentar o intérprete BeanShell :

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));
marciowerner
fonte
1
Você pode me dizer como usar o BeanShell no adnroid Studio.
Hanni
1
Hanni - este post pode ajudá-lo adicionando BeanShell ao seu androidstudio projeto: stackoverflow.com/questions/18520875/...
marciowerner
14

Você pode avaliar expressões facilmente se seu aplicativo Java já acessar um banco de dados, sem usar outros JARs.

Alguns bancos de dados exigem o uso de uma tabela fictícia (por exemplo, a tabela "dupla" do Oracle) e outros permitem avaliar expressões sem "selecionar" em qualquer tabela.

Por exemplo, no Sql Server ou Sqlite

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

e no Oracle

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

A vantagem de usar um banco de dados é que você pode avaliar muitas expressões ao mesmo tempo. Além disso, a maioria dos bancos de dados permitirá que você use expressões altamente complexas e também terá várias funções extras que podem ser chamadas conforme necessário.

No entanto, o desempenho pode sofrer se muitas expressões únicas precisarem ser avaliadas individualmente, principalmente quando o banco de dados estiver localizado em um servidor de rede.

A seguir, aborda o problema de desempenho, até certo ponto, usando um banco de dados Sqlite na memória.

Aqui está um exemplo completo de trabalho em 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();

Obviamente, você pode estender o código acima para lidar com vários cálculos ao mesmo tempo.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");
DAB
fonte
5
Diga Olá para injeção de SQL!
cyberz
Depende do que você usa o banco de dados. Se você quiser ter certeza, poderá criar facilmente um banco de dados sqlite vazio, especificamente para a avaliação matemática.
DAB
4
@cyberz Se você usar meu exemplo acima, o Sqlite criará um banco de dados temporário na memória. Veja stackoverflow.com/questions/849679/…
DAB
11

Este artigo discute várias abordagens. Aqui estão as 2 principais abordagens mencionadas no artigo:

JEXL do Apache

Permite scripts que incluem referências a objetos 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);

Use o mecanismo javascript incorporado no 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();
    }
}
Brad Parks
fonte
4
Resuma as informações do artigo, caso o link esteja quebrado.
quer
Atualizei a resposta para incluir bits relevantes do artigo
Brad Parks
1
na prática, o JEXL é lento (usa introspecção de beans), apresenta problemas de desempenho com multithreading (cache global)
Nishi
É bom saber @Nishi! - Meu caso de uso era para depurar coisas em ambientes ativos, mas não fazia parte do aplicativo implantado normal.
Brad Parks
10

Outra maneira é usar o Spring Expression Language ou SpEL, que faz muito mais junto com a avaliação de expressões matemáticas, portanto, talvez um pouco exagero. Você não precisa usar a estrutura Spring para usar esta biblioteca de expressões, pois é independente. Copiando exemplos da documentação do 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

Leia mais exemplos concisos de SpEL aqui e os documentos completos aqui

Faheem Sohail
fonte
8

se vamos implementá-lo, podemos usar o algoritmo abaixo: -

  1. Embora ainda haja tokens para serem lidos,

    1.1 Obtenha o próximo token. 1.2 Se o token for:

    1.2.1 Um número: empurre-o para a pilha de valores.

    1.2.2 Uma variável: obtenha seu valor e empurre para a pilha de valores.

    1.2.3 Um parêntese esquerdo: empurre-o para a pilha do operador.

    1.2.4 Um parêntese direito:

     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 Um operador (chame-o 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. Enquanto a pilha do operador não estiver vazia, 1 Retire o operador da pilha do operador. 2 Coloque a pilha de valores duas vezes, obtendo dois operandos. 3 Aplique o operador aos operandos, na ordem correta. 4 Empurre o resultado na pilha de valores.

  3. Nesse ponto, a pilha do operador deve estar vazia e a pilha de valores deve ter apenas um valor, que é o resultado final.

Prashant Gautam
fonte
3
Esta é uma exposição não creditada do algoritmo Dijkstra Shunting-yard . Crédito onde o crédito é devido.
Marquês de Lorne
7

Esta é outra alternativa interessante https://github.com/Shy-Ta/expression-evaluator-demo

O uso é muito simples e faz o trabalho, por exemplo:

  ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");  
  assertEquals(BigDecimal.valueOf(11), evalExpr.eval()); 
Escorpião
fonte
6

Parece que o JEP deveria fazer o trabalho

Bozho
fonte
4

Eu acho que de qualquer maneira que você faça isso, isso envolverá muitas declarações condicionais. Porém, para operações únicas, como nos seus exemplos, você pode limitar a 4 se declarações com algo como

String math = "1+4";

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

Fica muito mais complicado quando você deseja lidar com várias operações como "4 + 5 * 6".

Se você está tentando construir uma calculadora, seria melhor passar cada seção do cálculo separadamente (cada número ou operador), e não como uma única string.

BruteForce
fonte
2
Fica muito mais complicado assim que você precisa lidar com várias operações, precedência do operador, parênteses, ... de fato, qualquer coisa que caracterize uma expressão aritmética real. Você não pode chegar lá a partir desta técnica.
Marquês de Lorne
4

É tarde demais para responder, mas me deparei com a mesma situação para avaliar a expressão em java, isso pode ajudar alguém

MVELfaz avaliação de expressões em tempo de execução, podemos escrever um código java Stringpara que seja avaliado nisso.

    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);
Saravana
fonte
Eu percorri e encontrou algumas funções aritméticas adicionais também tratadas aqui github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/...
thecodefather
Awsome! Isso salvou o meu dia. Graças
Sarika.S
4

Você pode dar uma olhada na estrutura Symja :

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

Observe que expressões definitivamente mais complexas podem ser avaliadas:

// 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
Laurent Magnin
fonte
4

Experimente o seguinte código de exemplo usando o mecanismo Javascript do JDK1.6 com manipulação de injeção de código.

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;
    }
 }
}
Bruce
fonte
4

Na verdade, isso complementa a resposta dada por @Boann. Ele tem um pequeno erro que faz com que "-2 ^ 2" dê um resultado incorreto de -4,0. O problema é o ponto em que a exponenciação é avaliada na dele. Apenas mova a exponenciação para o bloco de parseTerm () e você ficará bem. Dê uma olhada no abaixo, que é a resposta de @ Boann ligeiramente modificada. A modificação está nos comentários.

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();
}
Romeo Sierra
fonte
-2^2 = -4é realmente normal, e não um bug. É agrupado como -(2^2). Experimente no Desmos, por exemplo. Seu código realmente apresenta vários bugs. A primeira é que ^não agrupa mais da direita para a esquerda. Em outras palavras, 2^3^2é suposto agrupar como 2^(3^2)porque ^é associativo à direita, mas suas modificações o tornam como grupo (2^3)^2. A segunda é que ^deveria ter maior precedência que *e /, mas suas modificações tratam da mesma forma. Consulte ideone.com/iN2mMa .
Radiodef 16/08/19
Então, o que você está sugerindo é que a exponenciação é melhor mantida onde estava, não é?
Romeo Sierra
Sim, é o que estou sugerindo.
Radiodef 16/08/19
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));
}

}

chejaras
fonte
1
Não lida com precedência do operador, ou vários operadores ou parênteses. Não use.
Marquês de Lorne
2

Que tal algo como isso:

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

e faça o mesmo para todos os outros operadores matemáticos.

konxie
fonte
9
Você deve ler sobre como escrever analisadores de expressões matemáticas eficientes. Existe uma metodologia de ciência da computação. Dê uma olhada no ANTLR, por exemplo. Se você pensar bem sobre o que escreveu, verá que coisas como (a + b / -c) * (e / f) não funcionarão com a sua ideia ou o código ficará super sujo e ineficiente.
precisa saber é o seguinte
2

Mais uma opção: https://github.com/stefanhaustein/expressionparser

Eu implementei isso para ter uma opção simples, mas flexível, para permitir ambos:

O TreeBuilder vinculado acima faz parte de um pacote de demonstração CAS que faz derivação simbólica. Há também um exemplo de intérprete BASIC e eu comecei a criar um intérprete TypeScript usando-o.

Stefan Haustein
fonte
2

Uma classe Java que pode avaliar expressões matemáticas:

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

    }

}
Efi G
fonte
2
Não manipula a precedência do operador corretamente. Existem maneiras padrão de fazer isso, e essa não é uma delas.
Marquês de Lorne
EJP, você pode apontar onde há um problema com a precedência do operador? Concordo plenamente com o fato de que essa não é a maneira padrão de fazê-lo. Como as formas padrão já foram mencionadas nos posts anteriores, a ideia era mostrar outra maneira de fazê-lo.
Efi G
2

Biblioteca externa como RHINO ou NASHORN pode ser usada para executar o javascript. E o javascript pode avaliar uma fórmula simples sem separar a string. Sem impacto no desempenho também se o código for escrito corretamente. Abaixo está um exemplo com 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);
}
Manish
fonte
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; }
   //.........
}
pedra
fonte