¿Cómo evaluar una expresión matemática dada en forma de cadena?


318

Estoy tratando de escribir una rutina Java para evaluar expresiones matemáticas simples a partir de Stringvalores como:

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

Quiero evitar muchas declaraciones if-then-else. ¿Cómo puedo hacer esto?


77
Recientemente escribí un analizador de expresiones matemáticas llamado exp4j que se publicó bajo la licencia de apache, puede consultarlo aquí: objecthunter.net/exp4j
fasseg

2
¿Qué tipo de expresiones permiten? ¿Solo expresiones de operador único? ¿Se permiten paréntesis?
Raedwald

3
También eche un vistazo al algoritmo de dos pilas de Dijkstra
Ritesh


3
¿Cómo puede esto ser considerado demasiado amplio? La evaluación de Dijkstra es la solución obvia aquí en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Respuestas:


376

Con JDK1.6, puede usar el motor Javascript incorporado.

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
Parece que hay un gran problema allí; Ejecuta un script, no evalúa una expresión. Para que quede claro, engine.eval ("8; 40 + 2"), salidas 42! Si desea un analizador de expresiones que también verifique la sintaxis, acabo de terminar uno (porque no encontré nada que se adapte a mis necesidades): Javaluator .
Jean-Marc Astesana

44
Como nota al margen, si necesita usar el resultado de esta expresión en otra parte de su código, puede convertir el resultado a un Doble como return (Double) engine.eval(foo);
sigue

38
Nota de seguridad: nunca debe usar esto en un contexto de servidor con la entrada del usuario. El JavaScript ejecutado puede acceder a todas las clases de Java y, por lo tanto, secuestrar su aplicación sin límite.
Boann

3
@Boann, te pido que me des una referencia sobre lo que dijiste (para estar seguro al 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();");- escribirá un archivo vía JavaScript en (por defecto) el directorio actual del programa
Boann

236

He escrito este evalmétodo para expresiones aritméticas para responder a esta pregunta. Realiza suma, resta, multiplicación, división, exponenciación (usando el ^símbolo) y algunas funciones básicas como sqrt. Admite la agrupación usando (... ), y obtiene la precedencia del operador y las reglas de asociatividad correctas.

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

Ejemplo:

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

Salida: 7.5 (que es correcto)


El analizador es un analizador de descenso recursivo , por lo que utiliza internamente métodos de análisis separados para cada nivel de precedencia del operador en su gramática. Lo mantuve breve para que sea fácil de modificar, pero aquí hay algunas ideas con las que puede expandirlo:

  • Variables:

    El bit del analizador que lee los nombres de las funciones también se puede cambiar fácilmente para manejar variables personalizadas, buscando nombres en una tabla de variables que se pasa al eval método, como a Map<String,Double> variables.

  • Compilación y evaluación separadas:

    ¿Qué pasa si, después de haber agregado soporte para variables, desea evaluar la misma expresión millones de veces con variables cambiadas, sin analizarla cada vez? Es posible. Primero defina una interfaz para usar para evaluar la expresión precompilada:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Ahora cambie todos los métodos que devuelven doubles, por lo que en cambio devuelven una instancia de esa interfaz. La sintaxis lambda de Java 8 funciona muy bien para esto. Ejemplo de uno de los métodos modificados:

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

    Eso construye un árbol recursivo de Expressionobjetos que representan la expresión compilada (un árbol de sintaxis abstracta ). Luego puede compilarlo una vez y evaluarlo repetidamente con diferentes valores:

    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());
        }
    }
  • Diferentes tipos de datos:

    En lugar de double, podría cambiar el evaluador para usar algo más poderoso BigDecimal, como una clase que implemente números complejos o números racionales (fracciones). Incluso podría usar Object, permitiendo una combinación de tipos de datos en expresiones, al igual que un lenguaje de programación real. :)


Todo el código en esta respuesta se libera al dominio público . ¡Que te diviertas!


1
Buen algoritmo, a partir de él logré implicar y operadores lógicos. Creamos clases separadas para funciones para evaluar una función, así que, como su idea de variables, creo un mapa con funciones y cuidando el nombre de la función. Cada función implementa una interfaz con un método eval (T rightOperator, T leftOperator), por lo que en cualquier momento podemos agregar características sin cambiar el código del algoritmo. Y es una buena idea hacer que funcione con tipos genéricos. ¡Gracias!
Vasile Bors

1
¿Puedes explicar la lógica detrás de este algoritmo?
iYonatan

1
Trato de dar una descripción de lo que entiendo del código escrito por Boann, y ejemplos descritos wiki. La lógica de este algoritmo a partir de las reglas de las órdenes de operación. 1. signo de operador | evaluación variable | llamada a funciones | paréntesis (sub-expresiones); 2. exponenciación; 3. multiplicación, división; 4. suma, resta;
Vasile Bors

1
Los métodos de algoritmo se dividen para cada nivel de orden de operaciones de la siguiente manera: parseFactor = 1. signo de operador | evaluación variable | llamada a funciones | paréntesis (sub-expresiones); 2. exponenciación; parseTerms = 3. multiplicación, división; parseExpression = 4. suma, resta. El algoritmo, llama a los métodos en orden inverso (parseExpression -> parseTerms -> parseFactor -> parseExpression (para sub-expresiones)), pero cada método a la primera línea llama al método al siguiente nivel, por lo que los métodos de orden de ejecución completo serán En realidad el orden normal de las operaciones.
Vasile Bors

1
Por ejemplo, el método parseExpression double x = parseTerm(); evalúa el operador izquierdo, después de esto for (;;) {...}evalúa las operaciones sucesivas del nivel de orden real (suma, resta). La misma lógica es y en el método parseTerm. ParseFactor no tiene el siguiente nivel, por lo que solo hay evaluaciones de métodos / variables o, en caso de parálisis, evalúe la subexpresión. El boolean eat(int charToEat)método verifica la igualdad del carácter del cursor actual con el carácter charToEat, si igual devuelve verdadero y mueve el cursor al siguiente carácter, utilizo el nombre 'aceptar' para ello.
Vasile Bors

34

La forma correcta de resolver esto es con un lexer y un analizador . Puede escribir versiones simples de estos usted mismo, o esas páginas también tienen enlaces a lexers y analizadores de Java.

Crear un analizador de descenso recursivo es un muy buen ejercicio de aprendizaje.


26

Para mi proyecto universitario, estaba buscando un analizador / evaluador que soporte tanto fórmulas básicas como ecuaciones más complicadas (especialmente operadores iterados). Encontré una biblioteca de código abierto muy agradable para JAVA y .NET llamada mXparser. Daré algunos ejemplos para que sientan algo sobre la sintaxis. Para obtener más instrucciones, visite el sitio web del proyecto (especialmente la sección de tutoriales).

https://mathparser.org/

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

https://mathparser.org/api/

Y pocos ejemplos

1 - Fórmula simple

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

2 - Argumentos y constantes definidos por el usuario

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 - Funciones definidas por el usuario

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 - Iteración

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

Encontrado recientemente: en caso de que desee probar la sintaxis (y ver el caso de uso avanzado), puede descargar la aplicación Scalar Calculator que funciona con mXparser.

Atentamente


Hasta ahora, esta es la mejor biblioteca de matemáticas que existe; simple para arrancar, fácil de usar y extensible. Definitivamente debería ser la mejor respuesta.
Trynkiewicz Mariusz

Encuentra la versión de Maven aquí .
izogfif

Descubrí que mXparser no puede identificar fórmulas ilegales, por ejemplo, '0/0' obtendrá un resultado como '0'. ¿Cómo puedo resolver este problema?
L

Acabo de encontrar la solución, expression.setSlientMode ()
lulijun

20

AQUÍ hay otra biblioteca de código abierto en GitHub llamada EvalEx.

A diferencia del motor de JavaScript, esta biblioteca se centra únicamente en la evaluación de expresiones matemáticas. Además, la biblioteca es extensible y admite el uso de operadores booleanos, así como paréntesis.


Esto está bien, pero falla cuando intentamos multiplicar valores de múltiplos de 5 o 10, por ejemplo, 65 * 6 da como resultado 3.9E + 2 ...
paarth batra

. Pero hay una manera de arreglar esto al convertirlo en int, es decir, int output = (int) 65 * 6, ahora resultará en 390
paarth batra

1
Para aclarar, eso no es un problema de la biblioteca, sino más bien un problema con la representación de números como valores de coma flotante.
DavidBittner

Esta biblioteca es realmente buena. @paarth batra Enviar a int eliminará todos los puntos decimales. Utilice esto en su lugar: expression.eval (). ToPlainString ();
einUsername


14

Puede evaluar expresiones fácilmente si su aplicación Java ya accede a una base de datos, sin usar ningún otro JAR.

Algunas bases de datos requieren que use una tabla ficticia (por ejemplo, la tabla "dual" de Oracle) y otras le permitirán evaluar expresiones sin "seleccionar" de ninguna tabla.

Por ejemplo, en SQL Server o SQLite

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

y en Oracle

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

La ventaja de usar una base de datos es que puede evaluar muchas expresiones al mismo tiempo. Además, la mayoría de las bases de datos le permitirán usar expresiones muy complejas y también tendrán una serie de funciones adicionales que se pueden invocar según sea necesario.

Sin embargo, el rendimiento puede verse afectado si es necesario evaluar individualmente muchas expresiones individuales, especialmente cuando la base de datos se encuentra en un servidor de red.

A continuación se aborda el problema de rendimiento hasta cierto punto, mediante el uso de una base de datos en memoria Sqlite.

Aquí hay un ejemplo de trabajo completo en 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();

Por supuesto, puede extender el código anterior para manejar múltiples cálculos al mismo tiempo.

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

55
¡Saluda a la inyección SQL!
cyberz

Depende de para qué use el DB. Si quiere estar seguro, puede crear fácilmente una base de datos sqlite vacía, específicamente para la evaluación matemática.
DAB

44
@cyberz Si usa mi ejemplo anterior, Sqlite creará una base de datos temporal en la memoria. Ver stackoverflow.com/questions/849679/…
DAB

11

Este artículo analiza varios enfoques. Aquí están los 2 enfoques clave mencionados en el artículo:

JEXL de Apache

Permite scripts que incluyen referencias 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 el motor javascript incrustado en el 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();
    }
}

44
Resuma la información del artículo, en caso de que se rompa el enlace.
DJClayworth

He actualizado la respuesta para incluir partes relevantes del artículo
Brad Parks,

1
en la práctica, JEXL es lento (usa introspección de beans), tiene problemas de rendimiento con multithreading (caché global)
Nishi

¡Es bueno saber @Nishi! - Mi caso de uso fue para depurar cosas en entornos en vivo, pero no es parte de la aplicación implementada normal.
Brad Parks

10

Otra forma es usar Spring Expression Language o SpEL, que hace mucho más junto con la evaluación de expresiones matemáticas, por lo tanto, puede ser un poco exagerado. No tiene que usar Spring Framework para usar esta biblioteca de expresiones, ya que es independiente. Copiar ejemplos de la documentación de 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

Lea más ejemplos concisos de SpEL aquí y los documentos completos aquí.


8

si vamos a implementarlo, podemos usar el siguiente algoritmo:

  1. Si bien todavía hay tokens para leer,

    1.1 Obtenga el siguiente token. 1.2 Si el token es:

    1.2.1 Un número: empujarlo a la pila de valores.

    1.2.2 Una variable: obtenga su valor y empuje a la pila de valores.

    1.2.3 Un paréntesis izquierdo: empújelo sobre la pila del operador.

    1.2.4 Un paréntesis correcto:

     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 Un operador (llámelo 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. Mientras la pila de operadores no está vacía, 1 saque el operador de la pila de operadores. 2 Pop la pila de valores dos veces, obteniendo dos operandos. 3 Aplique el operador a los operandos, en el orden correcto. 4 Empuje el resultado en la pila de valores.

  3. En este punto, la pila de operadores debe estar vacía, y la pila de valores debe tener solo un valor, que es el resultado final.


3
Esta es una exposición sin acreditar del algoritmo del patio de maniobras de Dijkstra . Crédito a quien crédito merece.
Marqués de Lorne



4

Creo que de cualquier manera que hagas esto implicará muchas declaraciones condicionales. Pero para operaciones individuales como en sus ejemplos, podría limitarlo a 4 si las declaraciones con algo como

String math = "1+4";

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

Se vuelve mucho más complicado cuando quieres lidiar con múltiples operaciones como "4 + 5 * 6".

Si está tratando de construir una calculadora, superaría cada paso del cálculo por separado (cada número u operador) en lugar de hacerlo como una sola cadena.


2
Se vuelve mucho más complicado tan pronto como tiene que lidiar con múltiples operaciones, precedencia de operadores, paréntesis, ... de hecho, cualquier cosa que caracterice una expresión aritmética real. No puedes llegar a partir de esta técnica.
Marqués de Lorne

4

Es demasiado tarde para responder, pero me encontré con la misma situación para evaluar la expresión en Java, podría ayudar a alguien

MVELhace una evaluación en tiempo de ejecución de las expresiones, podemos escribir un código java Stringpara evaluarlo en esto.

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

Recorrí y encontré algunas funciones aritméticas adicionales también manejadas aquí github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Impresionante! Me salvó el día. Gracias
Sarika.S

4

Puede echar un vistazo al marco de Symja :

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

Tenga en cuenta que definitivamente se pueden evaluar expresiones más complejas:

// 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

Pruebe el siguiente código de muestra utilizando el motor Javascript de JDK1.6 con manejo de inyección 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;
    }
 }
}

4

Esto en realidad complementa la respuesta dada por @Boann. Tiene un pequeño error que hace que "-2 ^ 2" dé un resultado erróneo de -4.0. El problema para eso es el punto en el que se evalúa la exponenciación en su. Simplemente mueva la exponenciación al bloque de parseTerm (), y todo estará bien. Eche un vistazo a la siguiente, que es la respuesta de @ Boann ligeramente modificada. La modificación está en los comentarios.

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 = -4es realmente normal y no es un error. Se agrupa como -(2^2). Pruébalo en Desmos, por ejemplo. Su código en realidad introduce varios errores. La primera es que ^ya no se agrupa de derecha a izquierda. En otras palabras, 2^3^2se supone que se debe agrupar como 2^(3^2)porque ^es asociativo correcto, pero sus modificaciones hacen que se agrupe como (2^3)^2. El segundo es que ^se supone que tiene mayor prioridad que *y /, pero sus modificaciones lo tratan igual. Ver ideone.com/iN2mMa .
Radiodef

Entonces, lo que está sugiriendo es que la exponenciación se mantiene mejor donde estaba, ¿no es así?
Romeo Sierra

Sí, eso es lo que estoy sugiriendo.
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
No maneja la precedencia del operador, o varios operadores, o paréntesis. No utilice.
Marqués de Lorne

2

Qué tal algo como esto:

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

y hacer lo mismo para cualquier otro operador matemático en consecuencia.


99
Debe leer sobre cómo escribir analizadores de expresiones matemáticas eficientes. Hay una metodología informática para ello. Echa un vistazo a ANTLR, por ejemplo. Si piensa bien sobre lo que escribió, verá que cosas como (a + b / -c) * (e / f) no funcionarán con su idea o el código será muy sucio e ineficiente.
Daniel Nuriyev


2

Otra opción más: https://github.com/stefanhaustein/expressionparser

He implementado esto para tener una opción simple pero flexible que permita ambos:

El TreeBuilder vinculado anteriormente es parte de un paquete de demostración de CAS que realiza una derivación simbólica. También hay un ejemplo de intérprete BÁSICO y he comenzado a construir un intérprete TypeScript usándolo.


2

Una clase de Java que puede evaluar expresiones 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)))"));

    }

}

2
No maneja la precedencia del operador correctamente. Hay formas estándar de hacer esto, y esta no es una de ellas.
Marqués de Lorne

EJP, ¿puede señalar dónde hay un problema con la precedencia del operador? Estoy totalmente de acuerdo con el hecho de que no es la forma estándar de hacerlo. Las formas estándar ya se mencionaron en publicaciones anteriores, la idea era mostrar otra forma de hacerlo.
Efi G

2

Se puede usar una biblioteca externa como RHINO o NASHORN para ejecutar JavaScript. Y JavaScript puede evaluar una fórmula simple sin dividir la cadena. Tampoco tiene impacto en el rendimiento si el código está bien escrito. A continuación se muestra un ejemplo con 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; }
   //.........
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.