Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
Evaluating a mathematical expression considering Operator Precedence in JavaScript
This article demonstrates how to evaluate mathematical expressions in JavaScript while respecting operator precedence. We'll implement a parser that handles the four basic arithmetic operators (+, -, *, /) and follows the standard order of operations.
Problem Statement
We need to write a JavaScript function that takes a mathematical expression as a string and returns its numerical result. The function must support:
Addition (+)
Subtraction (-)
Multiplication (*)
Division (/) as floating-point division
The key challenge is handling operator precedence: multiplication and division must be evaluated before addition and subtraction.
Solution Using Shunting Yard Algorithm
We'll use the Shunting Yard algorithm to convert the infix expression to postfix notation, then evaluate it. This approach naturally handles operator precedence and associativity.
const exp = '6 - 4 + 2 * 3';
const findResult = (exp = '') => {
const digits = '0123456789.';
const operators = ['+', '-', '*', '/', 'negate'];
const legend = {
'+': { pred: 2, func: (a, b) => { return a + b; }, assoc: "left" },
'-': { pred: 2, func: (a, b) => { return a - b; }, assoc: "left" },
'*': { pred: 3, func: (a, b) => { return a * b; }, assoc: "left" },
'/': { pred: 3, func: (a, b) => {
if (b != 0) { return a / b; } else { return 0; }
}, assoc: "left" },
'negate': { pred: 4, func: (a) => { return -1 * a; }, assoc: "right" }
};
exp = exp.replace(/\s/g, '');
let operations = [];
let outputQueue = [];
let ind = 0;
let str = '';
while (ind < exp.length) {
let ch = exp[ind];
if (operators.includes(ch)) {
if (str !== '') {
outputQueue.push(new Number(str));
str = '';
}
if (ch === '-') {
if (ind == 0) {
ch = 'negate';
} else {
let nextCh = exp[ind+1];
let prevCh = exp[ind-1];
if ((digits.includes(nextCh) || nextCh === '(' || nextCh === '-') &&
(operators.includes(prevCh) || exp[ind-1] === '(')) {
ch = 'negate';
}
}
}
if (operations.length > 0) {
let topOper = operations[operations.length - 1];
while (operations.length > 0 && legend[topOper] &&
((legend[ch].assoc === 'left' && legend[ch].pred <= legend[topOper].pred) ||
(legend[ch].assoc === 'right' && legend[ch].pred < legend[topOper].pred))) {
outputQueue.push(operations.pop());
topOper = operations[operations.length - 1];
}
}
operations.push(ch);
} else if (digits.includes(ch)) {
str += ch;
} else if (ch === '(') {
operations.push(ch);
} else if (ch === ')') {
if (str !== '') {
outputQueue.push(new Number(str));
str = '';
}
while (operations.length > 0 && operations[operations.length - 1] !== '(') {
outputQueue.push(operations.pop());
}
if (operations.length > 0) { operations.pop(); }
}
ind++;
}
if (str !== '') { outputQueue.push(new Number(str)); }
outputQueue = outputQueue.concat(operations.reverse());
let res = [];
while (outputQueue.length > 0) {
let ch = outputQueue.shift();
if (operators.includes(ch)) {
if (ch === 'negate') {
res.push(legend[ch].func(res.pop()));
} else {
let [num2, num1] = [res.pop(), res.pop()];
res.push(legend[ch].func(num1, num2));
}
} else {
res.push(ch);
}
}
return res.pop().valueOf();
};
console.log(findResult(exp));
8
How It Works
The algorithm works in two phases:
Phase 1 - Parsing: Convert infix to postfix notation using operator precedence rules. Higher precedence operators (* and /) are processed before lower precedence ones (+ and -).
Phase 2 - Evaluation: Process the postfix expression using a stack, applying operators to operands in the correct order.
Testing with Different Expressions
console.log(findResult('6 - 4')); // Simple subtraction
console.log(findResult('2 + 3 * 4')); // Precedence test
console.log(findResult('10 / 2 + 3')); // Division with addition
console.log(findResult('(2 + 3) * 4')); // Parentheses
console.log(findResult('-5 + 3')); // Negative numbers
2 14 8 20 -2
Key Features
The implementation handles several important cases:
Operator Precedence: * and / are evaluated before + and -
Negative Numbers: Distinguishes between subtraction and negative signs
Parentheses: Supports grouping with parentheses
Division by Zero: Returns 0 instead of throwing an error
Conclusion
The Shunting Yard algorithm provides a robust solution for evaluating mathematical expressions with proper operator precedence. This approach can be easily extended to support additional operators or functions as needed.
