How to code a calculator in javascript without eval

24,415

For a simple calculator with only 5 operators (^, *, /, +, -) and no parentheses, you can do something like this. First, it is convenient to turn the string into an array of numbers and operators. Then, we go through the array looking for each operator in order of precedence, and applying the operator to the numbers preceding and following the it.

function tokenize(s) {
    // --- Parse a calculation string into an array of numbers and operators
    const r = [];
    let token = '';
    for (const character of s) {
        if ('^*/+-'.indexOf(character) > -1) {
            if (token === '' && character === '-') {
                token = '-';
            } else {
                r.push(parseFloat(token), character);
                token = '';
            }
        } else {
            token += character;
        }
    }
    if (token !== '') {
        r.push(parseFloat(token));
    }
    return r;
}

function calculate(tokens) {
    // --- Perform a calculation expressed as an array of operators and numbers
    const operatorPrecedence = [{'^': (a, b) => Math.pow(a, b)},
               {'*': (a, b) => a * b, '/': (a, b) => a / b},
               {'+': (a, b) => a + b, '-': (a, b) => a - b}];
    let operator;
    for (const operators of operatorPrecedence) {
        const newTokens = [];
        for (const token of tokens) {
            if (token in operators) {
                operator = operators[token];
            } else if (operator) {
                newTokens[newTokens.length - 1] = 
                    operator(newTokens[newTokens.length - 1], token);
                operator = null;
            } else {
                newTokens.push(token);
            }
        }
        tokens = newTokens;
    }
    if (tokens.length > 1) {
        console.log('Error: unable to resolve calculation');
        return tokens;
    } else {
        return tokens[0];
    }
}
const calculateButton = document.getElementById('calculate');
const userInput = document.getElementById('userInput');
const result = document.getElementById('result');
calculateButton.addEventListener('click', function() {
    result.innerHTML = "The answer is " + calculate(tokenize(userInput.value));
});
<input type="text" id="userInput" />
<input type="button" value="Calculate" id="calculate" />
<div id="result"></div>

(jsfiddle). To allow parentheses, you could tell the calculate function to check for parentheses before it starts looking for any of the other operators, then recursively call itself on the expression within each set of parentheses. The parsing function can also be improved e.g. removing any white space and dealing with errors.

Share:
24,415
spb
Author by

spb

Updated on July 12, 2022

Comments

  • spb
    spb almost 2 years

    So, I've searched high and low, and I can't find an answer to this. I've attempted it about three times and gotten a basic one cranked out by basically storing the input in an array as a string, parsing the numbers, then switching on the operator, in order to evaluate the integers, but I'm having a really hard time figuring out the chaining logic. Does anyone have any suggestions? Of maybe even just the psuedocode? I really don't want to use eval. Thanks a lot

  • S1awek
    S1awek over 7 years
    I noticed one bug: when you try to calculate something like this: 10.01 - 11 you'll get 0.9900000000000002which is wrong.
  • Stuart
    Stuart about 7 years
    @TomSki This is a general problem with decimal calculations in JS and most languages stackoverflow.com/questions/1458633/…. It can be dealt with using a library like decimal.js github.com/MikeMcl/decimal.js, as in this updated version of the calculator jsfiddle.net/10wn7bun/2
  • S1awek
    S1awek about 7 years
    Thanks, for last few days I was trying to find solution for this problem, and I couldn't work out any simple way to do so. Seems like decimal.js is best option for me.
  • Ghos3t
    Ghos3t about 7 years
    There is a issue with this code, it seems to do the calculations right to left, ignoring operator precedence. For example if you do the calculation "40-5+3*2" the answer comes as 29, when it should be 41 (The multiplication should be done first, then since + & - have same precendence they should be calulated left to right).
  • Stuart
    Stuart about 7 years
    @user258365 Thanks for spotting, the problem was actually giving higher precedence to + than - (which I didn't think would matter, but it does...) Now fixed. Version using decimal.js is jsfiddle.net/10wn7bun/4
  • Harvey
    Harvey over 4 years
    Note : If you use the comma as separator, don't forget to add this in parseCalculationString function in parseFloat : current.replace(/,/g, '.').
  • Tigerrrrr
    Tigerrrrr about 4 years
    @Ghos3t | This is a very late response, but Mathematicians generally use BIDMAS. Brackets, Indices, Divide, Multiply, Add, Subtract, specifically in that order. It's not going left to right or right to left but it's using BIDMAS.
  • Stuart
    Stuart about 4 years
    @Tigerrrrr Ghos3t's comment was in response to an error in an earlier version of the answer, which has been fixed. Operations do go left to right within the order (1) exponent (2) multiplication/division (3) addition/subtraction. For example 40-5+6 = (40-5)+6 and not 40-(5+6) -- the leftmost operation is done first. Addition is not necessarily done before subtraction and BIDMAS etc can be confusing in that respect. en.m.wikipedia.org/wiki/Order_of_operations