Calculadora
Para desarrollar una calculadora que evalúe expresiones matemáticas, lo primero es entender cómo se deben procesar y calcular las operaciones, siguiendo el orden correcto (precedencia de operaciones), así como la correcta gestión de los paréntesis.
1. Tokenización (Lexing):¶
El primer paso es convertir la cadena de entrada (la expresión) en una secuencia de "tokens". Un "token" es la representación de un elemento que tiene un significado dentro de la expresión, como números, operadores, paréntesis, etc.
Ejemplo:
- Entrada:
"3 + 5 * (2 - 8)". - Tokens generados:
[3, '+', 5, '*', '(', 2, '-', 8, ')'].
2. Uso de un algoritmo de Evaluación:¶
Para evaluar la expresión, hay varios algoritmos que puedes usar, pero los más comunes son:
a. Notación infija (como la que usas normalmente en las matemáticas): Para evaluar correctamente las expresiones infijas (como la usual), es necesario respetar la precedencia de los operadores y el uso de paréntesis. Esto se hace generalmente utilizando una pila (stack) para gestionar los operadores y los operandos, y una cola para almacenar el resultado.
b. Algoritmo de "Shunting Yard" de Dijkstra: Este es un algoritmo muy conocido para convertir una expresión infija en notación polaca inversa (postfija) y luego evaluarla fácilmente. Básicamente, este algoritmo resuelve la precedencia de los operadores y los paréntesis mediante el uso de dos estructuras de datos: una pila para los operadores y una lista (o cola) para la salida.
c. Evaluación de una expresión postfija: Una vez que se tiene la notación postfija (si usas el algoritmo de Shunting Yard), la evaluación se realiza procesando la lista de tokens de izquierda a derecha, usando una pila para los operandos.
3. Precedencia de los Operadores:¶
Los operadores tienen diferentes prioridades:
- Paréntesis:
() - Exponentes:
^(si es que los estás manejando) - Multiplicación y división:
*,/ - Suma y resta:
+,-
Debemos asegurarnos de procesar las operaciones de acuerdo a su precedencia.
4. Manejo de los Paréntesis:¶
Los paréntesis tienen la máxima prioridad. Si encuentras un paréntesis de apertura (, lo debes agregar a la pila. Si encuentras un paréntesis de cierre ), debes realizar todas las operaciones hasta encontrar el paréntesis de apertura correspondiente, y después continuar.
5. Evaluación paso a paso (Evaluación en la notación infija con pila):¶
- Si el token es un número, lo añades a la pila.
- Si el token es un operador, primero comparas su precedencia con el operador en la cima de la pila. Si la precedencia del operador actual es mayor, lo empujas a la pila. Si es menor o igual, debes hacer la operación con los dos operandos de la pila antes de agregar el operador a la pila.
- Cuando encuentres un paréntesis de cierre
), debes realizar las operaciones de la pila hasta encontrar el paréntesis de apertura(.
Ejemplo de Evaluación:¶
Supongamos que tenemos la siguiente expresión:
Entrada: "3 + 5 * (2 - 8)".
Pasos:
- Tokenizamos:
[3, '+', 5, '*', '(', 2, '-', 8, ')']. - Evaluamos la expresión con una pila (con precedencia de operadores):
3→ apilamos.+→ apilamos.5→ apilamos.*→ apilamos.(→ apilamos.2→ apilamos.-→ apilamos.8→ apilamos.)→ comenzamos a resolver lo que está entre paréntesis:2 - 8 = -6.
- Ahora tenemos la expresión
3 + 5 * -6. Continuamos con la multiplicación, ya que tiene mayor precedencia:5 * -6 = -30.
- Finalmente:
3 + (-30) = -27.
Resultado: -27.
6. Manejo de Errores:¶
Es importante manejar errores comunes, como:
- División por cero.
- Sintaxis incorrecta (por ejemplo, paréntesis sin cerrar).
- Operadores consecutivos sin operandos entre ellos.
7. Optimización y mejoras (opcional):¶
Si lo deseas, puedes implementar funciones adicionales como la capacidad de manejar números con decimales, exponentes o incluso funciones matemáticas (como sin, cos, tan, etc.).
Resumen:¶
- Tokenización: Divide la entrada en tokens significativos.
- Conversión (si es necesario): Convierte a notación postfija con el algoritmo de Shunting Yard.
- Evaluación: Resuelve la expresión respetando la precedencia de los operadores, gestionando los paréntesis.
- Errores: Maneja errores en la entrada para evitar cálculos incorrectos.
Este enfoque garantizará que puedas manejar correctamente las expresiones matemáticas de una calculadora.
Claro, te puedo proporcionar un ejemplo de código en Python que implementa una calculadora usando el algoritmo de Shunting Yard para convertir una expresión infija a postfija (notación polaca inversa) y luego evaluarla.
Código en Python:¶
# Precedencia de los operadores
precedencia = {
'+': 1,
'-': 1,
'*': 2,
'/': 2,
'^': 3
}
# Función para verificar si un carácter es un número
def es_numero(c):
return c.isdigit() or c == '.'
# Función para realizar la operación entre dos números
def operar(operando1, operando2, operador):
if operador == '+':
return operando1 + operando2
elif operador == '-':
return operando1 - operando2
elif operador == '*':
return operando1 * operando2
elif operador == '/':
if operando2 == 0:
raise ValueError("División por cero")
return operando1 / operando2
elif operador == '^':
return operando1 ** operando2
# Función para convertir la expresión infija a notación postfija (Shunting Yard)
def infijo_a_postfijo(expresion):
salida = [] # Cola de salida (postfija)
pila = [] # Pila para los operadores
i = 0
while i < len(expresion):
c = expresion[i]
if c == ' ':
i += 1
continue
if es_numero(c): # Si es un número, lo agregamos a la salida
numero = ""
while i < len(expresion) and (es_numero(expresion[i]) or expresion[i] == '.'):
numero += expresion[i]
i += 1
salida.append(float(numero))
continue
if c == '(': # Paréntesis de apertura
pila.append(c)
elif c == ')': # Paréntesis de cierre
while pila and pila[-1] != '(':
salida.append(pila.pop())
pila.pop() # Eliminar el '(' de la pila
elif c in precedencia: # Si es un operador
while pila and pila[-1] != '(' and precedencia[pila[-1]] >= precedencia[c]:
salida.append(pila.pop())
pila.append(c)
i += 1
while pila: # Vaciar los operadores restantes en la pila
salida.append(pila.pop())
return salida
# Función para evaluar la expresión postfija
def evaluar_postfijo(postfijo):
pila = []
for token in postfijo:
if isinstance(token, (int, float)): # Si es un número, lo apilamos
pila.append(token)
else: # Si es un operador, realizamos la operación
operando2 = pila.pop()
operando1 = pila.pop()
resultado = operar(operando1, operando2, token)
pila.append(resultado)
return pila[0] # El resultado final estará en la pila
# Función principal para evaluar una expresión matemática
def evaluar_expresion(expresion):
# Paso 1: Convertir la expresión infija a postfija
postfijo = infijo_a_postfijo(expresion)
# Paso 2: Evaluar la expresión postfija
return evaluar_postfijo(postfijo)
# Ejemplo de uso
expresion = "3 + 5 * (2 - 8)"
resultado = evaluar_expresion(expresion)
print(f"Resultado de '{expresion}' es: {resultado}")
Explicación del código:¶
-
precedencia: Un diccionario que define la precedencia de los operadores. Los operadores con mayor precedencia deben realizarse primero. -
es_numero(c): Función que verifica si un carácter es un número o un punto decimal, utilizado para reconocer los números en la expresión. -
operar(operando1, operando2, operador): Función que realiza la operación matemática correspondiente entre dos operandos. -
infijo_a_postfijo(expresion): Función que convierte la expresión infija (con paréntesis) a una notación postfija usando el algoritmo de Shunting Yard. Usa una pila para los operadores y una lista para la salida (expresión postfija). -
evaluar_postfijo(postfijo): Función que evalúa una expresión en notación postfija. Utiliza una pila para manejar los operandos y aplicar los operadores en el orden adecuado. -
evaluar_expresion(expresion): Función principal que convierte la expresión infija a postfija y luego la evalúa.
Ejemplo de salida:¶
Si ejecutas el código con la expresión "3 + 5 * (2 - 8)", el resultado será:
Resultado de '3 + 5 * (2 - 8)' es: -27.0
Este código es extensible y maneja correctamente la precedencia de los operadores y los paréntesis. Además, maneja la división por cero y puede ser fácilmente modificado para soportar más operadores o funciones matemáticas.
Código en Java:¶
import java.util.Stack;
public class Calculadora {
// Precedencia de los operadores
private static final java.util.Map<Character, Integer> precedencia = new java.util.HashMap<>();
static {
precedencia.put('+', 1);
precedencia.put('-', 1);
precedencia.put('*', 2);
precedencia.put('/', 2);
precedencia.put('^', 3);
}
// Función para verificar si un carácter es un número
public static boolean esNumero(char c) {
return Character.isDigit(c) || c == '.';
}
// Función para realizar la operación entre dos números
public static double operar(double operando1, double operando2, char operador) {
switch (operador) {
case '+': return operando1 + operando2;
case '-': return operando1 - operando2;
case '*': return operando1 * operando2;
case '/':
if (operando2 == 0) {
throw new ArithmeticException("División por cero");
}
return operando1 / operando2;
case '^': return Math.pow(operando1, operando2);
default: throw new IllegalArgumentException("Operador desconocido: " + operador);
}
}
// Función para convertir la expresión infija a postfija (Shunting Yard)
public static java.util.List<String> infijoApostfijo(String expresion) {
java.util.List<String> salida = new java.util.ArrayList<>();
Stack<Character> pila = new Stack<>();
for (int i = 0; i < expresion.length(); i++) {
char c = expresion.charAt(i);
if (c == ' ') {
continue; // Ignorar espacios
}
if (esNumero(c)) { // Si es un número, lo agregamos a la salida
StringBuilder numero = new StringBuilder();
while (i < expresion.length() && (esNumero(expresion.charAt(i)) || expresion.charAt(i) == '.')) {
numero.append(expresion.charAt(i));
i++;
}
salida.add(numero.toString());
i--; // Volver atrás porque el bucle incrementará 'i' más de lo necesario
} else if (c == '(') { // Paréntesis de apertura
pila.push(c);
} else if (c == ')') { // Paréntesis de cierre
while (!pila.isEmpty() && pila.peek() != '(') {
salida.add(String.valueOf(pila.pop()));
}
pila.pop(); // Eliminar '(' de la pila
} else if (precedencia.containsKey(c)) { // Si es un operador
while (!pila.isEmpty() && pila.peek() != '(' && precedencia.get(pila.peek()) >= precedencia.get(c)) {
salida.add(String.valueOf(pila.pop()));
}
pila.push(c);
}
}
// Vaciar los operadores restantes en la pila
while (!pila.isEmpty()) {
salida.add(String.valueOf(pila.pop()));
}
return salida;
}
// Función para evaluar la expresión postfija
public static double evaluarPostfijo(java.util.List<String> postfijo) {
Stack<Double> pila = new Stack<>();
for (String token : postfijo) {
if (token.matches("-?\\d+(\\.\\d+)?")) { // Si es un número, lo apilamos
pila.push(Double.parseDouble(token));
} else { // Si es un operador, realizamos la operación
double operando2 = pila.pop();
double operando1 = pila.pop();
double resultado = operar(operando1, operando2, token.charAt(0));
pila.push(resultado);
}
}
return pila.pop(); // El resultado final estará en la pila
}
// Función principal para evaluar una expresión matemática
public static double evaluarExpresion(String expresion) {
// Paso 1: Convertir la expresión infija a postfija
java.util.List<String> postfijo = infijoApostfijo(expresion);
// Paso 2: Evaluar la expresión postfija
return evaluarPostfijo(postfijo);
}
// Ejemplo de uso
public static void main(String[] args) {
String expresion = "3 + 5 * (2 - 8)";
double resultado = evaluarExpresion(expresion);
System.out.println("Resultado de '" + expresion + "' es: " + resultado);
}
}
Explicación del código:¶
-
precedencia: Un mapa (HashMap) que define la precedencia de los operadores. Los operadores con mayor precedencia deben realizarse antes que los de menor precedencia. -
esNumero(c): Función que verifica si un carácter es un número o un punto decimal. Es útil para identificar los operandos en la expresión. -
operar(operando1, operando2, operador): Función que realiza la operación matemática correspondiente entre dos operandos. Maneja operaciones básicas como suma, resta, multiplicación, división y potencia. -
infijoApostfijo(expresion): Convierte la expresión infija (con operadores y paréntesis) en una lista de tokens en notación postfija usando el algoritmo de Shunting Yard. Los operadores se apilan y se agregan a la salida cuando es necesario. -
evaluarPostfijo(postfijo): Esta función evalúa una expresión matemática en notación postfija. Utiliza una pila para realizar las operaciones. -
evaluarExpresion(expresion): Función principal que convierte la expresión infija a postfija y luego la evalúa, devolviendo el resultado.
Ejemplo de salida:¶
Si ejecutas el código con la expresión "3 + 5 * (2 - 8)", el resultado será:
Resultado de '3 + 5 * (2 - 8)' es: -27.0
Explicación de cómo funciona:¶
-
La expresión
"3 + 5 * (2 - 8)"es primero convertida a postfija:
["3", "5", "2", "8", "-", "*", "+"]. -
Luego, la expresión en notación postfija se evalúa utilizando una pila.
Este código maneja correctamente las operaciones básicas de la calculadora y permite el uso de paréntesis para definir la jerarquía de las operaciones. Además, maneja errores como la división por cero. Puedes extenderlo fácilmente para agregar más operadores o funciones matemáticas.