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


82

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

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


5
非常相似,但可能不是您要的:(Function("return 1+1;"))()
浓汤

Answers:



22

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

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

更复杂的数学使eval更具吸引力,当然也更容易编写。


2
+1-可能比我的处理方法更通用,但是它对我的情况不起作用,因为我可能有类似1 + -2的内容,并且我也希望正则表达式也排除无效的语句(我想您的语句会允许像“ + 3 + 4 +”之类的东西)
wheresrhys

我在下面发布了更新的答案,其中的正则表达式较短,并且允许在运算符之间
留有

17

有人必须解析该字符串。如果不是解释器(通过eval),则需要是您,编写一个解析例程以提取数字,运算符以及您要在数学表达式中支持的其他任何内容。

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


1
困扰我的不是安全性(我已经有一个正则表达式用于工作),更多的是浏览器的负担,因为我必须处理很多这样的字符串。自定义解析器是否可能比eval()更快?
wheresrhys

11
@wheresrhys:为什么您认为用JS编写的解析器会比提供的解析器(经过优化,可能是用C或C ++编写)更快?
Mehrdad Afshari 2010年

4
评估是迄今为止最快的方法。但是,正则表达式通常不足以确保安全性。
levik 2010年

1
@wheresrhys:为什么会有很多这样的字符串?它们是由程序生成的吗?如果是这样,最简单的方法是在将结果转换为字符串之前计算结果。否则,这是您自己编写解析器的时间。
菲尔H

12

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

1
只要您只需要加法和减法,这就是完美的。这么少的代码,那么多的产品!放心,它已经被很好地使用了:)
塔科马塔尔奥特曼

10

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

如果要以任意精度处理数字,也可以使用那些大数字库进行算术运算。


8

我一直在寻找用于评估数学表达式的JavaScript库,并找到了两个有希望的候选人:

  • JavaScript Expression Evaluator:更小,更轻巧。允许代数表达式,替换和许多函数。

  • mathjs:也允许复数,矩阵和单位。内置供浏览器内的JavaScript和Node.js使用。


现在,我已经测试了JavaScript Expression Evaluator,这似乎很糟糕。(mathjs可能也很麻烦,但对于我的目的来说似乎太大了,我也喜欢JSEE中的替换功能。)
Itangalo 2014年

7

我最近在C#中完成了此操作(不Eval()适合我们...),方法是使用反向波兰符号(很简单)来评估表达式。困难的部分实际上是解析字符串并将其转换为反向波兰符号。我使用了Shunting Yard算法,因为在Wikipedia和伪代码上有一个很好的例子。我发现实现这两者非常简单,如果您还没有找到解决方案或正在寻找替代方案,我建议您这样做。


您可以提供一些示例或Wikipedia的链接吗?
LetynSOFT '16

@LetynSOFT伪代码,可以发现这里
Mayonnaise2124

6

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

剩下的唯一“要做”是确保它在+&-之前计算*&/。稍后将添加该功能,但是现在这满足了我的需要...

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

3

简约典雅 Function()

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

parse("1+2+3"); 


你能解释一下它是如何工作的吗?我是这种语法的
新手

Function(“ return(1 + 2 + 3)”)(); -其匿名功能。我们只是执行参数(函数体)。Function(“ {return(1 + 2 + 3)}”)();
Aniket Kudale

好的,如何解析字符串?&($ {str})) -----()`到底是什么?
pageNotfoUnd

我不认为这比评估更好。在运行此服务器端之前,请注意parse('process.exit()')
巴斯蒂

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


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编译器实例负载相比,运行此脚本要舒服得多


1
尽管return不能在表达式内部使用,但sum("+1")返回NaN
Gumbo'3

始终预言return是否必须在三元表达式内。我想排除“ +1”,因为尽管“应该”以数字形式计算,但它实际上并不是日常意义上数学求和的一个示例。我的代码旨在评估和过滤允许的字符串。
wheresrhys 2010年

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>


2

我相信parseIntES6在这种情况下可能会有所帮助

==>以这种方式:

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

这里最主要的是parseInt与运算符解析数字。可以根据需要修改代码。


2

您可以使用Github维护良好的库, 该库可在Nodejs和Browser运行,其速度比此处提供的其他替代库更快

用法

 mexp = require('math-expression-evaluator')
 var value = mexp.eval(exp);  

完整文件


2
请向图书馆透露您的隶属关系。它由名为“ bugwheels94”的用户维护,这是您的用户名。
GalaxyCat105

2
看来您已链接到此处链接的GitHub存储库。链接到您所隶属的对象时,必须在您的帖子中披露该从属关系。在没有从属关系披露的情况下,从技术上讲,它是垃圾邮件。请参阅:什么表示“良好”的自我提升?帮助中心则是自我促进。披露必须是明确的,但不必是正式的。当出现这种情况时,披露可能只是诸如“……,我所贡献的存储库”之类,等等。我已经编辑了您的帖子以包括披露。
Makyen

@Makyen请不要强加您的意见。我找不到写在任何地方的免费软件包都必须在您提供的2个链接中披露的任何地方。我本该出于礼貌,但是现在经过不必要的编辑后,我不愿意这样做。这个软件包解决了OP的问题,我已经解释了如何解决。休息没关系
bugwheels94

1
@ bugwheels94链接到的内容是免费还是商业都没有关系。如果您链接或推广您所隶属的事物,则需要披露,除非在某些非常有限的情况下,这绝对不适用于此答案。披露从属关系的要求已经很长时间了。MSO和MSE都在Meta上进行了多次讨论。抱歉,您不喜欢我对帖子的最小修改。进行编辑是侵入性最小的选择。我之所以选择它,是因为除了没有披露之外,这是一个合理的答案。
Makyen


1

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

只需为您的输出输入添加一个属性,例如data-ac =“(#firstinput +#secondinput)”

无需任何初始化,仅添加data-ac属性即可。它将自动找出动态添加的元素

用输出添加“ Rs”,只需在大括号中添加data-ac =“ {Rs}(#firstinput +#secondinput)”


1
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

1
那么减法,乘法和除法呢?为什么要乘以num1?
nathanfranke

感谢您指出@nathanfranke我已经更新了答案,以使其更通用。现在,它支持所有4种操作。1的倍数是将其从字符串转换为数字。我们也可以通过执行+ num来实现。
Rushikesh Bharad

0

这是一种类似于jMichael的算法解决方案,它逐个字符地循环遍历表达式的每个字符并逐步跟踪左/运算符/右。该函数在每次找到操作符后都会累积结果。此版本仅支持'+'和'-'运算符,但被编写为与其他运算符一起扩展。注意:在循环之前,我们将'currOp'设置为'+',因为我们假设表达式以正浮点数开头。实际上,总的来说,我假设输入与来自计算器的输入相似。

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.