How to code a calculator in javascript without eval
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.
spb
Updated on July 12, 2022Comments
-
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 over 7 yearsI noticed one bug: when you try to calculate something like this:
10.01 - 11
you'll get0.9900000000000002
which is wrong. -
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 about 7 yearsThanks, 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 about 7 yearsThere 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 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 over 4 yearsNote : If you use the comma as separator, don't forget to add this in
parseCalculationString
function in parseFloat :current.replace(/,/g, '.')
. -
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 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