Evaluating a string as a mathematical expression in JavaScript

JavascriptStringMathNumbers

Javascript Problem Overview


How do I parse and evaluate a mathematical expression in a string (e.g. '1+1') without invoking eval(string) to yield its numerical value?

With that example, I want the function to accept '1+1' and return 2.

Javascript Solutions


Solution 1 - Javascript

You can use the JavaScript Expression Evaluator library, which allows you to do stuff like:

Parser.evaluate("2 ^ x", { x: 3 });

Or mathjs, which allows stuff like:

math.eval('sin(45 deg) ^ 2');

I ended up choosing mathjs for one of my projects.

Solution 2 - Javascript

You can do + or - easily:

function addbits(s) {
  var total = 0,
      s = s.match(/[+\-]*(\.\d+|\d+(\.\d+)?)/g) || [];
      
  while (s.length) {
    total += parseFloat(s.shift());
  }
  return total;
}

var string = '1+23+4+5-30';
console.log(
  addbits(string)
)

More complicated math makes eval more attractive- and certainly simpler to write.

Solution 3 - Javascript

Somebody has to parse that string. If it's not the interpreter (via eval) then it'll need to be you, writing a parsing routine to extract numbers, operators, and anything else you want to support in a mathematical expression.

So, no, there isn't any (simple) way without eval. If you're concerned about security (because the input you're parsing isn't from a source you control), maybe you can check the input's format (via a whitelist regex filter) before passing it to eval?

Solution 4 - Javascript

An alternative to the excellent answer by @kennebec, using a shorter regular expression and allowing spaces between operators

function addbits(s) {
    var total = 0;
    s = s.replace(/\s/g, '').match(/[+\-]?([0-9\.\s]+)/g) || [];
    while(s.length) total += parseFloat(s.shift());
    return total;
}

Use it like

addbits('5 + 30 - 25.1 + 11');

Update

Here's a more optimised version

function addbits(s) {
    return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
        .reduce(function(sum, value) {
	        return parseFloat(sum) + parseFloat(value);
        });
}

Solution 5 - Javascript

Simple and elegant with Function()

function parse(str) {
  return Function(`'use strict'; return (${str})`)()
}

parse("1+2+3"); 

Solution 6 - Javascript

I created BigEval for the same purpose.
In solving expressions, it performs exactly same as Eval() and supports operators like %, ^, &, ** (power) and ! (factorial). You are also allowed to use functions and constants (or say variables) inside the expression. The expression is solved in PEMDAS order which is common in programming languages including JavaScript.

var Obj = new BigEval();
var result = Obj.exec("5! + 6.6e3 * (PI + E)"); // 38795.17158152233
var result2 = Obj.exec("sin(45 * deg)**2 + cos(pi / 4)**2"); // 1
var result3 = Obj.exec("0 & -7 ^ -7 - 0%1 + 6%2"); //-7

It can also be made to use those Big Number libraries for arithmetic in case you are dealing with numbers with arbitrary precision.

Solution 7 - Javascript

I went looking for JavaScript libraries for evaluating mathematical expressions, and found these two promising candidates:

  • JavaScript Expression Evaluator: Smaller and hopefully more light-weight. Allows algebraic expressions, substitutions and a number of functions.

  • mathjs: Allows complex numbers, matrices and units as well. Built to be used by both in-browser JavaScript and Node.js.

Solution 8 - Javascript

This is a little function I threw together just now to solve this issue - it builds the expression by analyzing the string one character at a time (it's actually pretty quick though). This will take any mathematical expression (limited to +,-,*,/ operators only) and return the result. It can handle negative values and unlimited number operations as well.

The only "to do" left is to make sure it calculates * & / before + & -. Will add that functionality later, but for now this does what I need...

/**
* Evaluate a mathematical expression (as a string) and return the result
* @param {String} expr A mathematical expression
* @returns {Decimal} Result of the mathematical expression
* @example
*    // Returns -81.4600
*    expr("10.04+9.5-1+-100");
*/ 
function expr (expr) {

    var chars = expr.split("");
    var n = [], op = [], index = 0, oplast = true;

    n[index] = "";

    // Parse the expression
    for (var c = 0; c < chars.length; c++) {

        if (isNaN(parseInt(chars[c])) && chars[c] !== "." && !oplast) {
            op[index] = chars[c];
            index++;
            n[index] = "";
            oplast = true;
        } else {
            n[index] += chars[c];
            oplast = false;
        }
    }

    // Calculate the expression
    expr = parseFloat(n[0]);
    for (var o = 0; o < op.length; o++) {
        var num = parseFloat(n[o + 1]);
        switch (op[o]) {
            case "+":
                expr = expr + num;
                break;
            case "-":
                expr = expr - num;
                break;
            case "*":
                expr = expr * num;
                break;
            case "/":
                expr = expr / num;
                break;
        }
    }

    return expr;
}

Solution 9 - Javascript

I've recently done this in C# (no Eval() for us...) by evaluating the expression in Reverse Polish Notation (that's the easy bit). The hard part is actually parsing the string and turning it into Reverse Polish Notation. I used the Shunting Yard algorithm, as there's a great example on Wikipedia and pseudocode. I found it really simple to implement both and I'd recommend this if you haven't already found a solution or are looking for alternatives.

Solution 10 - Javascript

You could use a for loop to check if the string contains any invalid characters and then use a try...catch with eval to check if the calculation throws an error like eval("2++") would.

function evaluateMath(str) {
  for (var i = 0; i < str.length; i++) {
    if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
      return NaN;
    }
  }
  
  
  try {
    return eval(str)
  } catch (e) {
    if (e.name !== 'SyntaxError') throw e
    return NaN;
  }
}

console.log(evaluateMath('2 + 6'))

or instead of a function, you could set Math.eval

Math.eval = function(str) {
  for (var i = 0; i < str.length; i++) {
    if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
      return NaN;
    }
  }
  
  
  try {
    return eval(str)
  } catch (e) {
    if (e.name !== 'SyntaxError') throw e
    return NaN;
  }
}

console.log(Math.eval('2 + 6'))

Solution 11 - Javascript

I've eventually gone for this solution, which works for summing positive and negative integers (and with a little modification to the regex will work for decimals too):

function sum(string) {
  return (string.match(/^(-?\d+)(\+-?\d+)*$/)) ? string.split('+').stringSum() : NaN;
}	

Array.prototype.stringSum = function() {
	var sum = 0;
	for(var k=0, kl=this.length;k<kl;k++)
	{
		sum += +this[k];
	}
	return sum;
}

I'm not sure if it's faster than eval(), but as I have to carry out the operation lots of times I'm far more comfortable runing this script than creating loads of instances of the javascript compiler

Solution 12 - Javascript

Try nerdamer

var result = nerdamer('12+2+PI').evaluate();
document.getElementById('text').innerHTML = result.text();

<script src="http://nerdamer.com/js/nerdamer.core.js"></script>
<div id="text"></div>

Solution 13 - Javascript

I believe that parseInt and ES6 can be helpful in this situation

let func = (str) => {
  let arr = str.split("");
  return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`
};

console.log(func("1+1"));

The main thing here is that parseInt parses the number with the operator. Code can be modified to the corresponding needs.

Solution 14 - Javascript

Try AutoCalculator https://github.com/JavscriptLab/autocalculate Calculate Inputs value and Output By using selector expressions

Just add an attribute for your output input like data-ac="(#firstinput+#secondinput)"

No Need of any initialization just add data-ac attribute only. It will find out dynamically added elements automatically

FOr add 'Rs' with Output just add inside curly bracket data-ac="{Rs}(#firstinput+#secondinput)"

Solution 15 - Javascript

const operatorToFunction = {
    "+": (num1, num2) => +num1 + +num2,
    "-": (num1, num2) => +num1 - +num2,
    "*": (num1, num2) => +num1 * +num2,
    "/": (num1, num2) => +num1 / +num2
}

const findOperator = (str) => {
    const [operator] = str.split("").filter((ch) => ["+", "-", "*", "/"].includes(ch))
    return operator;
}

const executeOperation = (str) => {
    const operationStr = str.replace(/[ ]/g, "");
    const operator = findOperator(operationStr);
    const [num1, num2] = operationStr.split(operator)
    return operatorToFunction[operator](num1, num2);
};

const addition = executeOperation('1 + 1'); // ans is 2
const subtraction = executeOperation('4 - 1'); // ans is 3
const multiplication = executeOperation('2 * 5'); // ans is 10
const division = executeOperation('16 / 4'); // ans is 4

Solution 16 - Javascript

eval was far too slow for me. So I developed an StringMathEvaluator(SME), that follows the order of operations and works for all arithmetic equations containing the following:

  • Integers
  • Decimals
  • Mathematical Operators: +-*/
  • Preferential Perenthesis: $operator ($expression) $operator
  • Variables: If and only if you define a global and/or local scope.
    • Format: [a-zA-Z][a-zA-Z0-9]*
    • Nest Variable Operator: $var1.$var2
    • Function Parenthesis: $functionId(...$commaSepArgs)
    • Array Brackets: $arrayId[index]
  • (Ignores Spaces)

Speed Test Results: (Ran within chromium browser)

                                      ~(80 - 99)% faster with reasonable expression complexity.

                     500000 iterations (SME/eval)

Integer Test '4'
(0.346/35.646)Sec - SME 99.03% faster

Simple Equation Test '4+-3'
(0.385/35.09)Sec - SME 98.9% faster

Complex Equation Test '(16 / 44 * 2) + ((4 + (4+3)-(12- 6)) / (2 * 8))'
(2.798/38.116)Sec - SME 92.66% faster

Variable Evaluation Test '2 + 5.5 + Math.round(Math.sqrt(Math.PI)) + values.one + values.two + values.four.nested'
(6.113/38.177)Sec - SME 83.99% faster

Example Usage:

Initialize:

Without Variables:

const math = new StringMathEvaluator();
const twentyOne = math.eval('11 + 10');
console.log('BlackJack' + twentyOne);
// BlackJack21

With Variables

const globalScope = {Math};
const math = new StringMathEvaluator(globalScope);

const localScope = {a: [[1, () => ({func: () => [17,13]})],[11,64,2]]};
const str = '((a[0][1]().func()[0] + a[0][1]().func()[1]) * a[1][2] - Math.sqrt(a[1][1]) - a[1][0]) / a[0][0]';
const fortyOne = math.eval(str, localScope);
console.log('Sum' + fortyOne);
// Sum41

SME:

class StringMathEvaluator {
  constructor(globalScope) {
    globalScope = globalScope || {};
    const instance = this;
    let splitter = '.';

    function resolve (path, currObj, globalCheck) {
      if (path === '') return currObj;
      try {
        if ((typeof path) === 'string') path = path.split(splitter);
        for (let index = 0; index < path.length; index += 1) {
          currObj = currObj[path[index]];
        }
        if (currObj === undefined && !globalCheck) throw Error('try global');
        return currObj;
      }  catch (e) {
        return resolve(path, globalScope, true);
      }
    }

    function multiplyOrDivide (values, operands) {
      const op = operands[operands.length - 1];
      if (op === StringMathEvaluator.multi || op === StringMathEvaluator.div) {
        const len = values.length;
        values[len - 2] = op(values[len - 2], values[len - 1])
        values.pop();
        operands.pop();
      }
    }

    const resolveArguments = (initialChar, func) => {
      return function (expr, index, values, operands, scope, path) {
        if (expr[index] === initialChar) {
          const args = [];
          let endIndex = index += 1;
          const terminationChar = expr[index - 1] === '(' ? ')' : ']';
          let terminate = false;
          let openParenCount = 0;
          while(!terminate && endIndex < expr.length) {
            const currChar = expr[endIndex++];
            if (currChar === '(') openParenCount++;
            else if (openParenCount > 0 && currChar === ')') openParenCount--;
            else if (openParenCount === 0) {
              if (currChar === ',') {
                args.push(expr.substr(index, endIndex - index - 1));
                index = endIndex;
              } else if (openParenCount === 0 && currChar === terminationChar) {
                args.push(expr.substr(index, endIndex++ - index - 1));
                terminate = true;
              }
            }
          }

          for (let index = 0; index < args.length; index += 1) {
            args[index] = instance.eval(args[index], scope);
          }
          const state = func(expr, path, scope, args, endIndex);
          if (state) {
            values.push(state.value);
            return state.endIndex;
          }
        }
      }
    };

    function chainedExpressions(expr, value, endIndex, path) {
      if (expr.length === endIndex) return {value, endIndex};
      let values = [];
      let offsetIndex;
      let valueIndex = 0;
      let chained = false;
      do {
        const subStr = expr.substr(endIndex);
        const offsetIndex = isolateArray(subStr, 0, values, [], value, path) ||
                            isolateFunction(subStr, 0, values, [], value, path) ||
                            (subStr[0] === '.' &&
                              isolateVar(subStr, 1, values, [], value));
        if (Number.isInteger(offsetIndex)) {
          value = values[valueIndex];
          endIndex += offsetIndex - 1;
          chained = true;
        }
      } while (offsetIndex !== undefined);
      return {value, endIndex};
    }

    const isolateArray = resolveArguments('[',
      (expr, path, scope, args, endIndex) => {
        endIndex = endIndex - 1;
        let value = resolve(path, scope)[args[args.length - 1]];
        return chainedExpressions(expr, value, endIndex, '');
      });

    const isolateFunction = resolveArguments('(',
      (expr, path, scope, args, endIndex) =>
          chainedExpressions(expr, resolve(path, scope).apply(null, args), endIndex - 1, ''));

    function isolateParenthesis(expr, index, values, operands, scope) {
      const char = expr[index];
      if (char === '(') {
        let openParenCount = 1;
        let endIndex = index + 1;
        while(openParenCount > 0 && endIndex < expr.length) {
          const currChar = expr[endIndex++];
          if (currChar === '(') openParenCount++;
          if (currChar === ')') openParenCount--;
        }
        const len = endIndex - index - 2;
        values.push(instance.eval(expr.substr(index + 1, len), scope));
        multiplyOrDivide(values, operands);
        return endIndex;
      }
    };

    function isolateOperand (char, operands) {
      switch (char) {
        case '*':
        operands.push(StringMathEvaluator.multi);
        return true;
        break;
        case '/':
        operands.push(StringMathEvaluator.div);
        return true;
        break;
        case '+':
        operands.push(StringMathEvaluator.add);
        return true;
        break;
        case '-':
        operands.push(StringMathEvaluator.sub);
        return true;
        break;
      }
      return false;
    }

    function isolateValueReg(reg, resolver, splitter) {
      return function (expr, index, values, operands, scope) {
        const match = expr.substr(index).match(reg);
        let args;
        if (match) {
          let endIndex = index + match[0].length;
          let value = resolver(match[0], scope);
          if (!Number.isFinite(value)) {
            const state = chainedExpressions(expr, scope, endIndex, match[0]);
            if (state !== undefined) {
              value = state.value;
              endIndex = state.endIndex;
            }
          }
          values.push(value);
          multiplyOrDivide(values, operands);
          return endIndex;
        }
      }
    }
    const isolateNumber = isolateValueReg(StringMathEvaluator.numReg, Number.parseFloat);
    const isolateVar = isolateValueReg(StringMathEvaluator.varReg, resolve);


    this.eval = function (expr, scope) {
      scope = scope || globalScope;
      const allowVars = (typeof scope) === 'object';
      let operands = [];
      let values = [];
      let prevWasOpperand = true;
      for (let index = 0; index < expr.length; index += 1) {
        const char = expr[index];
        if (prevWasOpperand) {
          let newIndex = isolateParenthesis(expr, index, values, operands, scope) ||
                        isolateNumber(expr, index, values, operands, scope) ||
                        (allowVars && isolateVar(expr, index, values, operands, scope));
          if (Number.isInteger(newIndex)) {
            index = newIndex - 1;
            prevWasOpperand = false;
          }
        } else {
          prevWasOpperand = isolateOperand(char, operands);
        }
      }
      let value = values[0];
      for (let index = 0; index < values.length - 1; index += 1) {
        value = operands[index](values[index], values[index + 1]);
        values[index + 1] = value;
      }
      return value;
    }
  }
}

StringMathEvaluator.numReg = /^(-|)[0-9\.]{1,}/;
StringMathEvaluator.varReg = /^((\.|)([a-zA-Z][a-zA-Z0-9\.]*))/;
StringMathEvaluator.multi = (n1, n2) => n1 * n2;
StringMathEvaluator.div = (n1, n2) => n1 / n2;
StringMathEvaluator.add = (n1, n2) => n1 + n2;
StringMathEvaluator.sub = (n1, n2) => n1 - n2;

Solution 17 - Javascript

The best way and easiest way is to use math.js library. Here some example code demonstrating how to use the library. Click here to fiddle around.

// functions and constants
math.round(math.e, 3)                // 2.718
math.atan2(3, -3) / math.pi          // 0.75
math.log(10000, 10)                  // 4
math.sqrt(-4)                        // 2i
math.derivative('x^2 + x', 'x')      // 2*x+1
math.pow([[-1, 2], [3, 1]], 2)
     // [[7, 0], [0, 7]]

// expressions
math.evaluate('1.2 * (2 + 4.5)')     // 7.8
math.evaluate('12.7 cm to inch')     // 5 inch
math.evaluate('sin(45 deg) ^ 2')     // 0.5
math.evaluate('9 / 3 + 2i')          // 3 + 2i
math.evaluate('det([-1, 2; 3, 1])')  // -7

// chaining
math.chain(3)
    .add(4)
    .multiply(2)
    .done() // 14

Solution 18 - Javascript

I made a small function to parse a math expression, containing +,/,-,*. I used if statements I think switch cases will be better. Firstly I separated the string into the operator and its numbers convert then from string to float then iterate through while performing the operation.

 const evaluate=(mathExpStr) => {
    mathExpStr.replace(/[+-\/*]$/, "");
    let regExp = /\d+/g;
    let valueArr = (mathExpStr.match(regExp) || []).map((val) =>
      Number.parseFloat(val)
    );
    let operatorArr = mathExpStr.match(/[+-\/*]/g) || [];
    return converter(valueArr, operatorArr)
  }

const converter = (arr,operators)=>{
  let arr2=[...arr]
  for(let i=0;i<arr.length;i++){
    let o;
    if(arr2.length<2){return arr2[0]}
    if(operators[i]=="+"){
      o=arr2[0]+arr2[1]
      arr2.splice(0, 2, o)
      console.log(o,arr2, operators[i])
    }
    if(operators[i]=="-"){
      o=arr2[0]-arr2[1]
      arr2.splice(0,2, o)
      console.log(o,arr2, operators[i])
    }
    if(operators[i]=="*"){
      o=arr2[0]*arr2[1]
      arr2.splice(0,2,o)
      console.log(o,arr2, operators[i])
    }
    if(operators[i]=="/"){
      o=arr2[0]/arr2[1]
      arr2.splice(0,2, o)
      console.log(o,arr2, operators[i])
    }
  }
}
// console.log(converter(valueArr, operatorArr))
console.log(evaluate("1+3+5+6-4*2/4"))

Solution 19 - Javascript

Here is an algorithmic solution similar to jMichael's that loops through the expression character by character and progressively tracks left/operator/right. The function accumulates the result after each turn it finds an operator character. This version only supports '+' and '-' operators but is written to be extended with other operators. Note: we set 'currOp' to '+' before looping because we assume the expression starts with a positive float. In fact, overall I'm making the assumption that input is similar to what would come from a calculator.

function calculate(exp) {
  const opMap = {
    '+': (a, b) => { return parseFloat(a) + parseFloat(b) },
    '-': (a, b) => { return parseFloat(a) - parseFloat(b) },
  };
  const opList = Object.keys(opMap);
  
  let acc = 0;
  let next = '';
  let currOp = '+';

  for (let char of exp) {
    if (opList.includes(char)) {
      acc = opMap[currOp](acc, next);
      currOp = char;
      next = '';
    } else {
      next += char;
    } 
  }
  
  return currOp === '+' ? acc + parseFloat(next) : acc - parseFloat(next);
}

Solution 20 - Javascript

Based on Aniket Kudale's parse

To add context variables to the expression

function parseExpr(str: string, params: any) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return Function(...names, `'use strict'; return (${str})`)(...vals);
}

example

> parseExpr('age > 50? x : x/2', {x: 40, age: 46})
20

> parseExpr('age > 50? x : x/2', {x: 40, age: 60})
40

Solution 21 - Javascript

Adding a simple version for +, -, / and *, taking float numbers in consideration. Inspired by @kennebec.

function addbits(s) {
  let total = 0;
  s = s.match(/[+\-\*\/]*(\.\d+|\d+(\.\d+)?)/g) || [];
      
  while (s.length) {
    const nv = s.shift();
    if (nv.startsWith('/')) {
      total /= parseFloat(nv.substring(1));
    } else if (nv.startsWith('*')) {
      total *= parseFloat(nv.substring(1));
    } else {
      total += parseFloat(nv);
    }
  }
  return total;
}

var string = '-2*3.5';
console.log(
  addbits(string)
)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwheresrhysView Question on Stackoverflow
Solution 1 - JavascriptRafael VegaView Answer on Stackoverflow
Solution 2 - JavascriptkennebecView Answer on Stackoverflow
Solution 3 - JavascriptbdukesView Answer on Stackoverflow
Solution 4 - JavascriptStefan GabosView Answer on Stackoverflow
Solution 5 - JavascriptAniket KudaleView Answer on Stackoverflow
Solution 6 - JavascriptAviView Answer on Stackoverflow
Solution 7 - JavascriptItangaloView Answer on Stackoverflow
Solution 8 - JavascriptjMichaelView Answer on Stackoverflow
Solution 9 - JavascriptRichKView Answer on Stackoverflow
Solution 10 - Javascriptshreyasm-devView Answer on Stackoverflow
Solution 11 - JavascriptwheresrhysView Answer on Stackoverflow
Solution 12 - JavascriptArchHaskellerView Answer on Stackoverflow
Solution 13 - JavascriptfuserView Answer on Stackoverflow
Solution 14 - JavascriptJustine JoseView Answer on Stackoverflow
Solution 15 - JavascriptRushikesh BharadView Answer on Stackoverflow
Solution 16 - JavascriptjozsefmorrisseyView Answer on Stackoverflow
Solution 17 - JavascriptSalim HamidiView Answer on Stackoverflow
Solution 18 - JavascriptYagi91View Answer on Stackoverflow
Solution 19 - JavascriptinternetrossView Answer on Stackoverflow
Solution 20 - JavascriptArne JenssenView Answer on Stackoverflow
Solution 21 - JavascriptFelipe N MouraView Answer on Stackoverflow