如何解析和评估字符串(例如'1+1'
)中的数学表达式而不调用eval(string)
其数值?
在该示例中,我希望函数接受'1+1'
并返回2
。
Answers:
您可以使用JavaScript Expression Evaluator库,该库允许您执行以下操作:
Parser.evaluate("2 ^ x", { x: 3 });
或mathjs,它允许类似以下内容:
math.eval('sin(45 deg) ^ 2');
我最终为我的一个项目选择了mathjs。
您可以轻松地执行+或-:
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更具吸引力,当然也更容易编写。
有人必须解析该字符串。如果不是解释器(通过eval
),则需要是您,编写一个解析例程以提取数字,运算符以及您要在数学表达式中支持的其他任何内容。
因此,没有,没有任何(简单的)方法eval
。如果您担心安全性(因为您解析的输入不是来自您控制的源),也许可以在将输入传递给eval
?之前检查输入的格式(通过白名单正则表达式过滤器)。
@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);
});
}
我出于相同的目的创建了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
如果要以任意精度处理数字,也可以使用那些大数字库进行算术运算。
我一直在寻找用于评估数学表达式的JavaScript库,并找到了两个有希望的候选人:
JavaScript Expression Evaluator:更小,更轻巧。允许代数表达式,替换和许多函数。
mathjs:也允许复数,矩阵和单位。内置供浏览器内的JavaScript和Node.js使用。
我最近在C#中完成了此操作(不Eval()
适合我们...),方法是使用反向波兰符号(很简单)来评估表达式。困难的部分实际上是解析字符串并将其转换为反向波兰符号。我使用了Shunting Yard算法,因为在Wikipedia和伪代码上有一个很好的例子。我发现实现这两者非常简单,如果您还没有找到解决方案或正在寻找替代方案,我建议您这样做。
我刚刚将其集成在一起以解决这个问题,这是一个小功能-它通过一次分析一个字符的字符串来构建表达式(尽管实际上非常快)。这将采用任何数学表达式(仅限于+,-,*,/运算符)并返回结果。它也可以处理负值和无数运算。
剩下的唯一“要做”是确保它在+&-之前计算*&/。稍后将添加该功能,但是现在这满足了我的需要...
/**
* 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;
}
简约典雅 Function()
function parse(str) {
return Function(`'use strict'; return (${str})`)()
}
parse("1+2+3");
) -----
()`到底是什么?
parse('process.exit()')
。
您可以使用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'))
我最终选择了该解决方案,该解决方案可用于对正整数和负整数求和(对正则表达式进行少许修改也可用于十进制):
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编译器实例负载相比,运行此脚本要舒服得多
return
不能在表达式内部使用,但sum("+1")
返回NaN。
试试AutoCalculator https://github.com/JavscriptLab/autocalculate 通过使用选择器表达式来计算输入值和输出
只需为您的输出输入添加一个属性,例如data-ac =“(#firstinput +#secondinput)”
无需任何初始化,仅添加data-ac属性即可。它将自动找出动态添加的元素
用输出添加“ Rs”,只需在大括号中添加data-ac =“ {Rs}(#firstinput +#secondinput)”
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
num
1?
这是一种类似于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);
}
(Function("return 1+1;"))()
。