在JavaScript中将字符串评估为数学表达式

时间:2010-02-16 20:18:11

标签: javascript string math numbers

如何解析和评估字符串中的数学表达式(例如'1+1')而不调用eval(string)来产生其数值?

通过该示例,我希望该函数接受'1+1'并返回2

19 个答案:

答案 0 :(得分:54)

您可以使用JavaScript Expression Evaluator library,这可以让您执行以下操作:

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

mathjs,允许这样的内容:

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

我最终为我的一个项目选择了mathjs。

答案 1 :(得分:20)

//您可以轻松地执行+或 -

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';
addbits(string)

更复杂的数学使得eval更具吸引力 - 当然也更简单。

答案 2 :(得分:17)

有人必须解析该字符串。如果它不是解释器(通过eval)那么它将需要你,编写一个解析例程来提取数字,运算符以及你想要在数学表达式中支持的任何其他东西。

所以,不,没有eval没有任何(简单)方法。如果您担心安全性(因为您正在解析的输入不是来自您控制的源),也许您可​​以在将输入格式(通过白名单正则表达式过滤器)检查之前将其传递给eval?< / p>

答案 3 :(得分:8)

我出于同样的目的创建了BigEval 在解决表达式时,它的执行与Eval()完全相同,并支持%,^,&amp;,**(power)等运算符! (阶乘)。 您还可以在表达式中使用函数和常量(或说变量)。该表达式在PEMDAS order中得到解决,这在包括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

如果您处理具有任意精度的数字,也可以使用那些Big Number库进行算术运算。

答案 4 :(得分:7)

我去寻找用于评估数学表达式的JavaScript库,并找到了这两个有希望的候选者:

  • JavaScript Expression Evaluator:更小,希望更多 重量轻。允许代数表达式,替换和a 功能数量。

  • mathjs:允许复数,矩阵和单位。 构建为供浏览器中的JavaScript和Node.js使用。

答案 5 :(得分:7)

@kennebec的优秀答案的替代方案,使用较短的正则表达式并允许操作符之间的空格

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

一样使用它
addbits('5 + 30 - 25.1 + 11');

<强>更新

这是一个更优化的版本

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

答案 6 :(得分:6)

我最近在C#中做了这个(我们没有Eval()...)通过用反向波兰表示法评估表达式(这很容易)。困难的部分实际上是解析字符串并将其转换为反向波兰表示法。我使用了Shunting Yard算法,因为在维基百科和伪代码上有一个很好的例子。我发现实现这两者非常简单,我建议如果你还没有找到解决方案或正在寻找替代方案。

答案 7 :(得分:4)

这是我刚才一起解决这个问题的一个小函数 - 它通过一次分析字符串一个字符来构建表达式(虽然它实际上非常快)。这将采用任何数学表达式(仅限于+, - ,*,/运算符)并返回结果。它也可以处理负值和无限数量的操作。

剩下的唯一“待办事项”是确保计算*&amp; /之前+&amp; - 。稍后会添加该功能,但是现在这就是我需要的......

/**
* 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;
}

答案 8 :(得分:4)

Function()

简单而优雅

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

parse("1+2+3"); 

答案 9 :(得分:3)

您可以使用for循环检查字符串是否包含任何无效字符,然后使用try ... catch和eval来检查计算是否引发错误,例如eval("2++")

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'))

或者代替功能,可以设置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'))

答案 10 :(得分:2)

我最终选择了这个解决方案,它可用于求和正整数和负整数(并且对正则表达式进行一些修改也适用于小数):

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

我不确定它是否比eval()快,但由于我必须多次执行操作,因此运行此脚本比创建大量javascript编译器实例更为舒适

答案 11 :(得分:2)

尝试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>

答案 12 :(得分:1)

尝试AutoCalculator https://github.com/JavscriptLab/autocalculate 使用选择器表达式计算输入值和输出

只需为输出输入添加一个属性即可 数据-AC = “(#firstinput +#secondinput)”

无需任何初始化只需添加data-ac属性。 它会自动找出动态添加的元素

为输出添加'Rs'只需在花括号内添加 数据-AC = “{}卢比(#firstinput +#secondinput)”

答案 13 :(得分:1)

我相信parseInt ES6 在这种情况下会有所帮助

  以这种方式

==&gt;

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

这里最重要的是parseInt用运算符解析数字。代码可以修改为相应的需求。

答案 14 :(得分:0)

const getAddition = (str) => {
  return str.split('+').reduce((total, num) => (total + num * 1), 0);
};

const addition = getAddition('1+1');

另外是2。

答案 15 :(得分:0)

这是一个类似于jMichael的算法解决方案,它逐个字符循环表达并逐步跟踪左/操作符/右。该函数在每次转弯后累积结果,找到一个操作员字符。此版本仅支持&#39; +&#39;和&#39; - &#39;运营商,但写作与其他运营商一起扩展。注意:我们设置了&#39; currOp&#39;到&#39; +&#39;在循环之前,因为我们假设表达式以正浮点数开始。实际上,总的来说,我假设输入类似于计算器的输入。

:before

答案 16 :(得分:0)

eval 对我来说太慢了。所以我开发了一个 StringMathEvaluator(SME),它遵循运算顺序并适用于包含以下内容的所有算术方程:

  • 整数
  • 十进制
  • 数学运算符:+-*/
  • 优先括号:$operator ($expression) $operator
  • 变量:当且仅当您定义全局和/或局部作用域。
    • 格式:[a-zA-Z][a-zA-Z0-9]*
    • 嵌套变量运算符:$var1.$var2
    • 函数括号:$functionId(...$commaSepArgs)
    • 数组括号:$arrayId[index]
  • (忽略空格)

速度测试结果:(在 Chrome 浏览器中运行)

~(80 - 99)% 快,表达复杂度合理。

                     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

示例用法:

初始化:

无变量:

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

带变量

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

中小企业:

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;

答案 17 :(得分:0)

基于 Aniket Kudale 的 parse

给表达式添加上下文变量

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

示例

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

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

答案 18 :(得分:0)

最好和最简单的方法是使用 math.js 库。 这里有一些示例代码演示了如何使用该库。点击 here 摆弄。

// 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