Cómo evaluar expresiones matemáticas ...

 

MSc. Alexander Borbón Alpízar.

   
Inicio  1  2  3  4  5  6  7  8  9  10  11  12 13 14

 

 

Función que evalúa expresiones

Para hacer este módulo se inicia declarando la función para evaluar, la cual recibe la expresión en notación postfija y el número que se va a evaluar (que es un double); la función devuelve el resultado de la evaluación (que también es un double). En este caso la función se llamará f para simular que se evalúa en una función f(x). Esta función lanzará una excepción (ArithmeticException) si encuentra un error en la expresión.

public double f(String expresionParseada, double x) throws ArithmeticException{
   Stack pilaEvaluar = new Stack(); //Pila de double's para evaluar
   double a, b;
   StringTokenizer tokens=new StringTokenizer(expresionParseada);
   String tokenActual;
   ...
        

En esta función se declaran algunas variables. En un principio se declara la pila que guardará los números (pilaEvaluar), luego se declaran dos números $a$ y $b$ en donde se guardarán los elementos que se sacan de la pila para ser operados.

El StringTokenizer es otra clase que se define en la librería java.util, ésta toma un texto y lo separa en unidades lexicográficas (llamadas lexemas o  "tokens'' en inglés), cada palabra separada por un espacio es un lexema; por ejemplo, el texto  "2 3 + sen'' tiene cuatro lexemas:  "2'',  "3'',  "+'' y  "sen''. El tokenActual guarda la unidad lexicográfica que se procesa en un momento dado.

Para saber si hay más lexemas en una expresión se utiliza tokens.hasMoreTokens() que devuelve True o False y para sacar el siguiente lexema se usa tokens.nextToken(). Así, lo que falta en nuestra función es ir pasando por los lexemas e ir haciendo cálculos o guardando números según corresponda, el código sería el siguiente

try{
   while(tokens.hasMoreTokens()){
      tokenActual=tokens.nextToken();
      ...
   }//while
}catch(EmptyStackException e){
   throw new ArithmeticException("Expresión mal parseada");
}catch(NumberFormatException e){
   throw new ArithmeticException("Expresión mal digitada");
}catch(ArithmeticException e){
   throw new ArithmeticException("Valor no real en la expresión");
}

a=((Double)pilaEvaluar.pop()).doubleValue();

if(!pilaEvaluar.empty())
   throw new ArithmeticException("Expresión mal digitada");

return a;

}//funcion f
        

Los bloques try-catch-throw son los que lanzan las excepciones si en algún momento se acabaron los elementos en la pila (EmptyStackException), si hubo algún número que no pudo entender (NumberFormatException) o si hubo algún cálculo que no corresponde a un número real (ArithmeticException).

El proceso de analizar lexemas se repetirá mientras hallan más lexemas while(tokens.hasMoreTokens()){ y se saca el siguiente lexema con la instrucción tokenActual=tokens.nextToken();.

Al final del proceso, sacamos el último valor de la pila con la instrucción a=((Double)pilaEvaluar.pop()). doubleValue(); lo que hace es sacar el elemento con pilaEvaluar.pop(), luego lo convierte a un objeto Double (recuerde que la pila trabaja con objetos) y, por último, calcula su valor double primitivo (con doubleValue()).

Si este no era el último valor en la pila, quiere decir que hubo un error al evaluar (faltaron operadores) y se lanza una excepción con el bloque

if(!pilaEvaluar.empty())
   throw new ArithmeticException("Expresión mal digitada");
        

Si no hubo ningún error entonces devuelve el valor encontrado.

Ahora veamos el bloque de código que hace falta dentro del while.

Si el lexema actual es alguno de los números "e'',  "pi'' o  "x'', simplemente lo metemos en la pila, tomemos por ejemplo el número  "e'':

   if(tokenActual.equals("e")){
      pilaEvaluar.push(new Double(Math.E));
        

Al meter el número Math.E en la pila (este número es un double) se debe introducir como un objeto Double, por eso se utiliza el código new Double.

Cuando se tiene que meter  "$x$'' lo que se hace es introducir en la pila el valor que recibe la función. En todos los siguientes casos de este if utilizamos elseif; en este caso se utilizó if por ser el primero.

Por otro lado, si el lexema siguiente es un operador que recibe dos números, entonces primero se saca los dos números de la pila, y se guarda el resultado de la operación en la pila, tomando como ejemplo la suma.

   }else if(tokenActual.equals("+")){
      b=((Double)pilaEvaluar.pop()).doubleValue();
      a=((Double)pilaEvaluar.pop()).doubleValue();
      pilaEvaluar.push(new Double(a+b));
        

De igual manera se hace para funciones con un solo número, como logaritmo.

   }else if(tokenActual.equals("ln")){
      a=((Double)pilaEvaluar.pop()).doubleValue();
      pilaEvaluar.push(new Double(Math.log(a)));
        

Por último, si no fue nada de lo anterior, la única posibilidad que queda es que sea un número, este caso se toma el lexema y se trata de convertir en un double con Double.parseDouble(tokenActual), si no puede automáticamente se lanza la excepción; el número se convierte en un nuevo objeto Double y se mete en la pila. El código quedaría

   }else{
      pilaEvaluar.push(new Double(Double.parseDouble( tokenActual)));
   }
        

Agregando los casos correspondientes para cada una de las funciones que se admiten, ya se tiene un evaluador de expresiones en notación postfija funcional.

En el programa de ejemplo que se muestra al final del artículo, se admite lo siguiente:

  • Números  "especiales'': e, pi, x.

  • Operadores: +, -, *, /, %, ^.

  • Funciones: ln, log, abs, sen, sin, cos, tan, sec, csc, cot, sgn, asen, asin, acos, atan, asec, acsc, acot, senh, sinh, cosh, tanh, sech, sech, coth, sqrt, asenh, asinh, acosh, atanh, asech, acsch, acoth, round.

  • Números reales, por ejemplo 2,15, -20,5, etc.

El evaluador se puede modificar para poder admitir la variable $y$ o la $z$ por si queremos evaluar funciones de varias variables, además se pueden agregar otras funciones con modificaciones mínimas (otra variable se manejaría igual que la $x$, para otra función habría que manejarla como las demás funciones con su cálculo correspondiente).

Este evaluador también lo sobrecargamos para que pueda admitir la forma f(x) que evaluaría la función en la última expresión parseada (recuerde que la última expresión parseada se guardaba en el módulo anterior en una variable global llamada ultimaParseada).

public double f(double x) throws ArithmeticException{
   try{
      return f(ultimaParseada,x);
   }catch(ArithmeticException e){
      throw e;
   }
}//Fin de la funcion f
        

Esta función se puede probar introduciendo cualquier expresión en notación postfija y un número para evaluar.

Uniendo estos dos programas, se tiene una poderosa herramienta para evaluar expresiones y funciones matemáticas.

Para poder usar esta clase dentro de un programa se debe crear un objeto (Obj) de tipo Parseador.

Parseador miparser = new Parseador();
        

Para parsear una expresión expr se escribe miparser.parsear(expr), la función devuelve un String con expr en notación postfija, además el programa también guarda de manera automática la última expresión parseada. Para evaluar el número $x$ en la expresión se utilizar miparser.f(x) para evaluar en la última expresión o se puede pasar una expresión en notación postfija escribiendo miparser.f(exprEnPostfija, x).

Así, por ejemplo, el siguiente es el código de un applet7 en donde se utiliza la clase Parseador

import java.applet.*;
import java.awt.*;

public class PruebaParseador extends java.applet.Applet {
  //Constructor del parseador
  Parseador miparser=new Parseador();
  //Expresión a parsear
  String expresion=new String();
  //Valor en el que se va a evaluar
  double valor=0;
  //Textfield donde se digita la expresión a parsear
  TextField inputexpresion = new TextField("x + 5");
  //Textfield donde se digita el valor a evaluar en la expresión
  TextField inputvalor = new TextField("0",5);
  //Botón para evaluar
  Button boton= new Button("Evaluar la expresión");
  //Resultado de parsear la expresión
  TextField outputparseo = new TextField("          ");
  //Resultado de la evaluación en la expresión
  TextField outputevaluar = new TextField("         ");
  //Label donde se dan los errores
  Label info = new Label("Información en extremo importante           ", Label.CENTER);

  public void init(){ //Todo se pone en el applet
    add(inputexpresion);
    add(inputvalor);
    add(boton);
    add(outputparseo);
    add(outputevaluar);
    add(info);
  }//init

  public boolean action(Event evt, Object arg){
    if (evt.target instanceof Button){ //Si se apretó el botón
      try{
        info.setText(""); //Se pone el Label de los errores vacío
        expresion=inputexpresion.getText(); //Se lee la expresión
        //Se lee el valor a evaluar
        valor=Double.valueOf(inputvalor.getText()).doubleValue();
        //Se parsea la expresión
        outputparseo.setText(miparser.parsear(expresion));
        //Se evalúa el valor y se redondea
        outputevaluar.setText(""+redondeo(miparser.f(valor),5));
      }catch(Exception e){ //Si hubo error lo pone en el Label correspondiente
        info.setText(e.toString());
      }
    }//if del botón
    return true;
  }//action

  /*
  *Se redondea un número con los decimales dados
  */
  private double redondeo(double numero, int decimales){
    return ((double)Math.round(numero*Math.pow(10,decimales)))/Math.pow(10,decimales);
  }

}//PolCero
        

El applet se puede ver bajar y ver funcionando en la sección correspondiente a los programas, si se copia el texto se debe pegar en un archivo que se llame PruebaParseador.java y se debe tener una página HTML de prueba para verlo, la más sencilla es una página que tenga por código:

<HTML>
<HEAD>
</HEAD>
<BODY>
<CENTER>
<APPLET
  code  = "PruebaParseador.class"
  width = "500"
  height    = "300"
  >
</APPLET>
</CENTER>
</BODY>
</HTML>
        

Este applet tiene dos cajas de texto (TextField), una es donde se digita la expresión a evaluar (inputexpresion) y la otra es donde se digita el valor a evaluar(inputvalor), por defecto estas cajas iniciar con ´´x+5'' y  "0'' respectivamente. El applet tiene un botón (boton), al ser presionado, se lee lo que el usuario escribió en las cajas de texto y se escribe el resultado de parsear la expresión con el texto outputparseo.setText(miparser.parsear(expresion));, observe que el código que hace la traducción es miparser.parsear(expresion) y el resultado se escribe en la etiqueta outputparseo; algo similar se hace con el resultado de la evaluación, sólo que antes se redondea el resultado con el código redondeo(miparser.f(valor),5)

 


Inicio 1  2  3  4  5  6  7  8  9  10  11  12 13 14


Cidse - Revista virtual Matemática, Educación e Internet - ITCR
Derechos Reservados