11

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.

What should I do? What could be pseudocode? I really don't want to use 'eval'.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
spb
  • 4,049
  • 6
  • 22
  • 30
  • 1
    How are you gonna use the calculator? Do you want like a main calculator function and you pass expressions as strings to it? Like this: `calculate("1 + 2 / 9")` – Jabari King Aug 30 '15 at 01:46
  • 1
    i'll take whatever works, at this point. it wasn't my intention because i thought evaluating at the end like that, with all of the operators might be even more of a pain, since i'd have to account for order of operations, but if you know of a way, i'd love if you'd share. i had it in mind to do something such that if a user entered `1+3*5-10%`, the script process would evaluate 1+3, and store it, then 5*5, store it, then 25-10, store it, then output 0.15. – spb Aug 30 '15 at 02:04
  • 1
    Yes. There would have to be some sort of logic to know where to split up the expression though. – Jabari King Aug 30 '15 at 02:05
  • 2
    A similar question on StackOverflow, but with more answers, is available [here](https://stackoverflow.com/q/6479236/319711). – Erik Vullings Jan 04 '21 at 16:53

1 Answers1

14

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 ('^*/+-'.includes(character)) {
            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 userInput =  document.getElementById('userInput');
userInput.focus();
userInput.addEventListener('input', function() {
    document.getElementById('result').innerHTML = "The answer is " + calculate(tokenize(userInput.value));
});
<input type="text" id="userInput" />
<div id="result"></div>

(Alternative version here). 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.

Stuart
  • 9,597
  • 1
  • 21
  • 30
  • I noticed one bug: when you try to calculate something like this: `10.01 - 11` you'll get `0.9900000000000002`which is wrong. – S1awek Jan 31 '17 at 15:59
  • 1
    @TomSki This is a general problem with decimal calculations in JS and most languages http://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript. It can be dealt with using a library like decimal.js https://github.com/MikeMcl/decimal.js, as in this updated version of the calculator http://jsfiddle.net/10wn7bun/2 – Stuart Feb 04 '17 at 06:06
  • 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. – S1awek Feb 06 '17 at 18:31
  • 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). – Ghos3t Mar 15 '17 at 08:17
  • @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 http://jsfiddle.net/10wn7bun/4/ – Stuart Mar 15 '17 at 22:05
  • Note : If you use the comma as separator, don't forget to add this in `parseCalculationString` function in **parseFloat** : `current.replace(/,/g, '.')`. – Harvey Jan 24 '20 at 12:35
  • 2
    @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. – Tigerrrrr Mar 22 '20 at 18:07
  • 1
    @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. https://en.m.wikipedia.org/wiki/Order_of_operations – Stuart Mar 23 '20 at 22:26