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.

Updated on: 2026-03-15T23:19:00+05:30

582 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements