DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

The Scientific Calculator UI Problem That Nobody Solves Well

Building a calculator in JavaScript is the classic beginner project. Building a good calculator that handles operator precedence negative numbers and edge cases correctly is surprisingly difficult.

The operator precedence problem

The simplest calculator implementation processes operations left to right:

2 + 3 * 4 = 20 (wrong)
Enter fullscreen mode Exit fullscreen mode

The correct answer is 14 because multiplication has higher precedence than addition. A proper calculator must implement operator precedence, which requires either the Shunting-Yard algorithm or recursive descent parsing.

function evaluate(expression) {
  // Tokenize
  const tokens = expression.match(/(\d+\.?\d*|[+\-*/^()])/g);

  // Shunting-Yard algorithm
  const output = [];
  const operators = [];
  const precedence = { '+': 1, '-': 1, '*': 2, '/': 2, '^': 3 };

  for (const token of tokens) {
    if (!isNaN(token)) {
      output.push(parseFloat(token));
    } else if (token === '(') {
      operators.push(token);
    } else if (token === ')') {
      while (operators[operators.length - 1] !== '(') {
        output.push(operators.pop());
      }
      operators.pop();
    } else {
      while (operators.length && 
             precedence[operators[operators.length - 1]] >= precedence[token]) {
        output.push(operators.pop());
      }
      operators.push(token);
    }
  }
  while (operators.length) output.push(operators.pop());

  // Evaluate RPN
  const stack = [];
  for (const token of output) {
    if (typeof token === 'number') {
      stack.push(token);
    } else {
      const b = stack.pop(), a = stack.pop();
      switch (token) {
        case '+': stack.push(a + b); break;
        case '-': stack.push(a - b); break;
        case '*': stack.push(a * b); break;
        case '/': stack.push(a / b); break;
        case '^': stack.push(Math.pow(a, b)); break;
      }
    }
  }
  return stack[0];
}
Enter fullscreen mode Exit fullscreen mode

The negative number problem

Is -3 a negative number or the unary minus operator applied to 3? Your parser needs to distinguish between:

  • Unary minus: -3 + 5 (negative three plus five)
  • Binary minus: 8 - 3 (eight minus three)
  • Implicit multiplication: 2(-3) (two times negative three)

The rule: a minus sign is unary if it appears at the start of the expression, after an operator, or after an opening parenthesis.

The display problem

Calculators need to show partial expressions as the user types. This means maintaining two representations: the visual expression ("2 + 3 ×") and the computable expression ("2 + 3 *").

For a full-featured calculator that handles precedence, parentheses, and scientific functions correctly, I built one at zovo.one/free-tools/online-calculator. It implements proper operator precedence and supports scientific functions.


I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.

Top comments (0)