Есть ли способ вычислить формулу, хранящуюся в строке в JavaScript, без использования eval
?
Обычно я бы сделал что-то вроде
var apa = "12/5*9+9.4*2";
alert(eval(apa));
Итак, кто-нибудь знает об альтернативах eval
?
Есть ли способ вычислить формулу, хранящуюся в строке в JavaScript, без использования eval
?
Обычно я бы сделал что-то вроде
var apa = "12/5*9+9.4*2";
alert(eval(apa));
Итак, кто-нибудь знает об альтернативах eval
?
Это именно то место, где вы должны использовать eval, или вам придется перебирать строку и генерировать числа. Для этого вам нужно будет использовать метод isNaN.
Mhh, вы можете использовать Function
-constructor:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
function evil(fn) {
return new Function('return ' + fn)();
}
console.log( evil('12/5*9+9.4*2') ); // => 40.4
Нет ничего плохого в eval, особенно для подобных случаев. Вы можете сначала дезинформировать строку с регулярным выражением, чтобы быть в безопасности:
// strip anything other than digits, (), -+/* and .
var str = "12/5*9+9.4*2".replace(/[^-()\d/*+.]/g, '');
alert(eval(str));
Eval был построен для таких условий.
Если бы вы хотели другой метод, вам нужно было бы использовать чистую реализацию Javascript именно того, что eval собирается сделать.
Вот быстрый базовый пример, который я придумал (обновлено (2011-06-26): очиститель с полями ввода).
http://jsfiddle.net/vol7ron/6cdfA/
Замечания:
Изменить (2017-05-26) для использования SO Snippet:
function calculate(input) {
var f = {
add: '+',
sub: '-',
div: '/',
mlt: '*',
mod: '%',
exp: '^'
};
// Create array for Order of Operation and precedence
f.ooo = [
[
[f.mlt],
[f.div],
[f.mod],
[f.exp]
],
[
[f.add],
[f.sub]
]
];
input = input.replace(/[^0-9%^*\/()\-+.]/g, ''); // clean up unnecessary characters
var output;
for (var i = 0, n = f.ooo.length; i < n; i++) {
// Regular Expression to look for operators between floating numbers or integers
var re = new RegExp('(\\d+\\.?\\d*)([\\' + f.ooo[i].join('\\') + '])(\\d+\\.?\\d*)');
re.lastIndex = 0; // take precautions and reset re starting pos
// Loop while there is still calculation for level of precedence
while (re.test(input)) {
output = _calculate(RegExp.$1, RegExp.$2, RegExp.$3);
if (isNaN(output) || !isFinite(output))
return output; // exit early if not a number
input = input.replace(re, output);
}
}
return output;
function _calculate(a, op, b) {
a = a * 1;
b = b * 1;
switch (op) {
case f.add:
return a + b;
break;
case f.sub:
return a - b;
break;
case f.div:
return a / b;
break;
case f.mlt:
return a * b;
break;
case f.mod:
return a % b;
break;
case f.exp:
return Math.pow(a, b);
break;
default:
null;
}
}
}
label {
display: inline-block;
width: 4em;
}
<div>
<label for="input">Equation: </label>
<input type="text" id="input" value="12/5*9+9.4*2-1" />
<input type="button"
value="calculate"
onclick="getElementById('result').value = calculate(getElementById('input').value)" />
</div>
<div>
<label for="result">Result: </label>
<input type="text" id="result" />
</div>
Если вы не хотите использовать eval, вам придется использовать существующую библиотеку оценщиков выражений.
http://silentmatt.com/javascript-expression-evaluator/
http://www.codeproject.com/KB/scripting/jsexpressioneval.aspx
Вы также можете свернуть один из них:)
Вот реализация алгоритма Shunting-ярда с дополнительной поддержкой операторов унарного префикса (например, -
) и постфикса (например !
), А также функциональных (например, sqrt()
) нотаций. Другие операторы/функции могут быть легко определены с помощью метода Calculation.defineOperator
:
"use strict";
class Calculation {
constructor() {
this._symbols = {};
this.defineOperator("!", this.factorial, "postfix", 6);
this.defineOperator("^", Math.pow, "infix", 5, true);
this.defineOperator("*", this.multiplication, "infix", 4);
this.defineOperator("/", this.division, "infix", 4);
this.defineOperator("+", this.last, "prefix", 3);
this.defineOperator("-", this.negation, "prefix", 3);
this.defineOperator("+", this.addition, "infix", 2);
this.defineOperator("-", this.subtraction, "infix", 2);
this.defineOperator(",", Array.of, "infix", 1);
this.defineOperator("(", this.last, "prefix");
this.defineOperator(")", null, "postfix");
this.defineOperator("min", Math.min);
this.defineOperator("sqrt", Math.sqrt);
}
// Method allowing to extend an instance with more operators and functions:
defineOperator(symbol, f, notation = "func", precedence = 0, rightToLeft = false) {
// Store operators keyed by their symbol/name. Some symbols may represent
// different usages: e.g. "-" can be unary or binary, so they are also
// keyed by their notation (prefix, infix, postfix, func):
if (notation === "func") precedence = 0;
this._symbols[symbol] = Object.assign({}, this._symbols[symbol], {
[notation]: {
symbol, f, notation, precedence, rightToLeft,
argCount: 1 + (notation === "infix")
},
symbol,
regSymbol: symbol.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
+ (/\w$/.test(symbol) ? "\\b" : "") // add a break if it a name
});
}
last(...a) { return a[a.length-1] }
negation(a) { return -a }
addition(a, b) { return a + b }
subtraction(a, b) { return a - b }
multiplication(a, b) { return a * b }
division(a, b) { return a / b }
factorial(a) {
if (a%1 || !(+a>=0)) return NaN
if (a > 170) return Infinity;
let b = 1;
while (a > 1) b *= a--;
return b;
}
calculate(expression) {
let match;
const values = [],
operators = [this._symbols["("].prefix],
exec = _ => {
let op = operators.pop();
values.push(op.f(...[].concat(...values.splice(-op.argCount))));
return op.precedence;
},
error = msg => {
let notation = match ? match.index : expression.length;
return '${msg} at ${notation}:\n${expression}\n${' '.repeat(notation)}^';
},
pattern = new RegExp(
// Pattern for numbers
"\\d+(?:\\.\\d+)?|"
// ...and patterns for individual operators/function names
+ Object.values(this._symbols)
// longer symbols should be listed first
.sort( (a, b) => b.symbol.length - a.symbol.length )
.map( val => val.regSymbol ).join('|')
+ "|(\\S)", "g"
);
let afterValue = false;
pattern.lastIndex = 0; // Reset regular expression object
do {
match = pattern.exec(expression);
const [token, bad] = match || [")", undefined],
notNumber = this._symbols[token],
notNewValue = notNumber && !notNumber.prefix && !notNumber.func,
notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix;
// Check for syntax errors:
if (bad || (afterValue ? notAfterValue : notNewValue)) return error("Syntax error");
if (afterValue) {
// We either have an infix or postfix operator (they should be mutually exclusive)
const curr = notNumber.postfix || notNumber.infix;
do {
const prev = operators[operators.length-1];
if (((curr.precedence - prev.precedence) || prev.rightToLeft) > 0) break;
// Apply previous operator, since it has precedence over current one
} while (exec()); // Exit loop after executing an opening parenthesis or function
afterValue = curr.notation === "postfix";
if (curr.symbol !== ")") {
operators.push(curr);
// Postfix always has precedence over any operator that follows after it
if (afterValue) exec();
}
} else if (notNumber) { // prefix operator or function
operators.push(notNumber.prefix || notNumber.func);
if (notNumber.func) { // Require an opening parenthesis
match = pattern.exec(expression);
if (!match || match[0] !== "(") return error("Function needs parentheses")
}
} else { // number
values.push(+token);
afterValue = true;
}
} while (match && operators.length);
return operators.length ? error("Missing closing parenthesis")
: match ? error("Too many closing parentheses")
: values.pop() // All done!
}
}
Calculation = new Calculation(); // Create a singleton
// I/O handling
function perform() {
const expr = document.getElementById('expr').value,
result = Calculation.calculate(expr);
document.getElementById('out').textContent = isNaN(result) ? result : '=' + result;
}
document.getElementById('expr').addEventListener('input', perform);
perform();
// Tests
const tests = [
{ expr: '1+2', expected: 3 },
{ expr: '1+2*3', expected: 7 },
{ expr: '1+2*3^2', expected: 19 },
{ expr: '1+2*2^3^2', expected: 1025 },
{ expr: '-3!', expected: -6 },
{ expr: '12---11+1-3', expected: -1 },
{ expr: 'min(2,1,3)', expected: 1 },
{ expr: '(2,1,3)', expected: 3 },
{ expr: '4-min(sqrt(2+2*7),9,5)', expected: 0 },
{ expr: '2,3,10', expected: 10 }
]
for (let {expr, expected} of tests) {
let result = Calculation.calculate(expr);
console.assert(result === expected, '${expr} should be ${expected}, but gives ${result}');
}
#expr { width: 100%; font-family: monospace }
Expression: <input id="expr" value="min(-1,0)+((sqrt(16)+(-4+7)!*---4)/2)^2^3"><p>
<pre id="out"></pre>
Вы не можете, в лучшем случае, вы можете сделать что-то повторное, например, разобрать числа, а затем отделить операции с помощью переключателя и сделать их. Кроме этого, я использовал бы eval в этом случае.
Это будет что-то вроде (реальная реализация будет несколько более сложной, особенно если вы рассмотрите использование скобок, но вы получите идею)
function operate(text) {
var values = text.split("+");
return parseInt(values[0]) + parseInt(values[1]);
}
alert(operate("9+2"));
Тем не менее, я считаю, что лучший выбор, который вы можете сделать, - использовать eval, учитывая, что вы можете доверять источнику строки.
Я потратил пару часов на реализацию всех арифметических правил без использования eval()
и, наконец, я опубликовал пакет для npm string-math. Все в описании. наслаждаться
Если вы ищете синтаксический эквивалент eval
, вы можете использовать new Function
. Существуют небольшие различия в области видимости, но они в основном ведут себя одинаково, включая подверженность большей части тех же самых угроз безопасности:
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)
Это решение также обрезает пробелы и проверяет дублирующие операторы
например, ' 1+ 2 *2'//5
но ' 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
}
Это оказывается более производительным, чем решение @vol7ron
. Проверьте этот JSBenchmark