Calculate string value in javascript, not using eval

Viewed 99582

Is there a way to calculate a formula stored in a string in JavaScript without using eval?

Normally I would do something like

var apa = "12/5*9+9.4*2";
alert(eval(apa));

So, does anyone know about alternatives to eval?

13 Answers

This solution also clips whitespaces and checks for duplicating operators

e.g. ' 1+ 2 *2' // 5 but ' 1 + +2* 2 ' // Error

function calcMe(str) {
  const noWsStr = str.replace(/\s/g, '');
  const operators = noWsStr.replace(/[\d.,]/g, '').split('');
  const operands = noWsStr.replace(/[+/%*-]/g, ' ')
                          .replace(/\,/g, '.')
                          .split(' ')
                          .map(parseFloat)
                          .filter(it => it);

  if (operators.length >= operands.length){
    throw new Error('Operators qty must be lesser than operands qty')
  };

  while (operators.includes('*')) {
    let opIndex = operators.indexOf('*');
    operands.splice(opIndex, 2, operands[opIndex] * operands[opIndex + 1]);
    operators.splice(opIndex, 1);
  };
  while (operators.includes('/')) {
    let opIndex = operators.indexOf('/');
    operands.splice(opIndex, 2, operands[opIndex] / operands[opIndex + 1]);
    operators.splice(opIndex, 1);
  };
  while (operators.includes('%')) {
    let opIndex = operators.indexOf('%');
    operands.splice(opIndex, 2, operands[opIndex] % operands[opIndex + 1]);
    operators.splice(opIndex, 1);
  };

  let result = operands[0];
  for (let i = 0; i < operators.length; i++) {
    operators[i] === '+' ? (result += operands[i + 1]) : (result -= operands[i + 1])
  }
  return result
}

This shows to be more performant than @vol7ron's solution. Check this JSBenchmark

I spent a couple of hours to implement all the arithmetical rules without using eval() and finally I published a package on npm string-math. Everything is in the description. Enjoy

If you're looking for a syntactical equivalent to eval, you could use new Function. There are slight differences regarding scoping, but they mostly behave the same, including exposure to much of the same security risks:

let str = "12/5*9+9.4*2"

let res1 = eval(str)
console.log('res1:', res1)

let res2 = (new Function('return '+str)())
console.log('res2:', res2)

Note : There is no library used in this solution purely hard coded

My solution takes into account of brackets also like 8+6(7(-1)) or 8+6(7(-1))

You can do these operations ^, *, /, +, -

To calculate a string use calculate(tokenize(pieval("8+6(7(-1))").join("")))

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];
    }
}

function pieval(input) {
  let openParenCount = 0;
  let myOpenParenIndex = 0;
  let myEndParenIndex = 0;
  const result = [];

  for (let i = 0; i < input.length; i++) {
    if (input[i] === "(") {
      if (openParenCount === 0) {
        myOpenParenIndex = i;

        // checking if anything exists before this set of parentheses
        if (i !== myEndParenIndex) {
            if(!isNaN(input[i-1])){
                result.push(input.substring(myEndParenIndex, i) + "*");
            }else{
                result.push(input.substring(myEndParenIndex, i));
            }
        }
      }
      openParenCount++;
    }

    if (input[i] === ")") {
      openParenCount--;
      if (openParenCount === 0) {
        myEndParenIndex = i + 1;

        // recurse the contents of the parentheses to search for nested ones
        result.push(pieval(input.substring(myOpenParenIndex + 1, i)));
      }
    }
  }

  // capture anything after the last parentheses
  if (input.length > myEndParenIndex) {
    result.push(input.substring(myEndParenIndex, input.length));
  }

  //console.log(cal(result))
  let response = cal(result);
  return result;
}

function cal(arr) {
  let calstr = "";
  for (let i = 0; i < arr.length; i++) {
    if (typeof arr[i] != "string") {
      if (cal(arr[i]) < 0) {
        arr[i] = `${cal(arr[i])}`;
      } else {
        arr[i] = `${cal(arr[i])}`;
      }
    }
    if (typeof arr[i] === "string") {
      calstr += arr[i];
    }
    if (i == arr.length - 1) {
      //console.log("cal" ,calstr,calculate(tokenize(calstr)) );
      return calculate(tokenize(calstr));
    }
  }
}
console.log(calculate(tokenize(pieval("8+6(7(-1))").join("")))); // ["1+",["2-",["3+4"]]]
console.log(calculate(tokenize(pieval("1+(1+(2(4/4))+4)").join("")))); // ["1+",["2-",["3+4"]]]

There is also an open source implementation on GitHub, evaluator.js, and an NPM package.

From the README: Evaluator.js is a small, zero-dependency module for evaluating mathematical expressions.

All major operations, constants, and methods are supported. Additionally, Evaluator.js intelligently reports invalid syntax, such as a misused operator, missing operand, or mismatched parentheses.

Evaluator.js is used by a desktop calculator application of the same name. See a live demo on the website.

stringToMath(str);

I am using stringToMath(str) instead of eval(), it is working alright to calculate simple string values.

Related