Оценка строки как математического выражения в JavaScript - PullRequest
56 голосов
/ 16 февраля 2010

Как проанализировать и оценить математическое выражение в строке (например, '1+1'), не вызывая eval(string) для получения его числового значения?

В этом примере я хочу, чтобы функция принимала '1+1' и возвращала 2.

Ответы [ 14 ]

54 голосов
/ 21 февраля 2013

Вы можете использовать библиотеку JavaScript Expression Evaluator , которая позволяет выполнять такие вещи, как:

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

Или mathjs , что позволяет такие вещи, как:

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

В итоге я выбрал mathjs для одного из моих проектов.

20 голосов
/ 16 февраля 2010

// Вы можете сделать + или - легко:

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 более привлекательным - и, конечно, его проще писать.

17 голосов
/ 16 февраля 2010

Кто-то должен разобрать эту строку. Если это не интерпретатор (через eval), тогда вы должны будете написать подпрограмму синтаксического анализа для извлечения чисел, операторов и всего, что вы хотите поддержать в математическом выражении.

Так что нет, нет никакого (простого) пути без eval. Если вы беспокоитесь о безопасности (поскольку ввод, который вы анализируете, не из источника, которым вы управляете), возможно, вы можете проверить формат ввода (через фильтр регулярных выражений белого списка), прежде чем передать его в eval?

8 голосов
/ 31 мая 2017

Альтернатива отличному ответу @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);
        });
}
8 голосов
/ 30 июня 2015

Я создал BigEval для той же цели.
При решении выражений он работает точно так же, как Eval(), и поддерживает такие операторы, как%, ^, &, ** (power) и! (факториал). Вы также можете использовать функции и константы (или, скажем, переменные) внутри выражения. Выражение решается в порядке 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

Можно также использовать эти библиотеки больших чисел для арифметики, если вы имеете дело с числами с произвольной точностью.

7 голосов
/ 11 февраля 2014

Я искал библиотеки JavaScript для оценки математических выражений и нашел два многообещающих кандидата:

  • Оценщик выражений JavaScript : меньше и, надеюсь, больше легкий вес. Позволяет алгебраические выражения, замены и количество функций.

  • mathjs : также позволяет использовать комплексные числа, матрицы и единицы. Создан для использования как в браузере JavaScript, так и в Node.js.

6 голосов
/ 14 марта 2010

Я недавно сделал это в C # (без Eval () для нас ...), оценивая выражение в обратной польской записи (это легко). Самая сложная часть - это анализ этой строки и превращение ее в обратную польскую запись. Я использовал алгоритм Shunting Yard, так как в Википедии и псевдокоде есть отличный пример. Мне было очень легко реализовать оба варианта, и я бы порекомендовал это, если вы еще не нашли решение или ищете альтернативы.

4 голосов
/ 02 мая 2013

Это небольшая функция, которую я сейчас собрал для решения этой проблемы - она ​​строит выражение, анализируя строку по одному символу за раз (хотя на самом деле это довольно быстро). Это примет любое математическое выражение (ограниченное только операторами +, -, *, /) и вернет результат. Он также может обрабатывать отрицательные значения и неограниченное количество операций.

Единственное, что нужно сделать, это убедиться, что он вычисляет * & / перед + & -. Позже добавлю эту функциональность, но сейчас она делает то, что мне нужно ...

/**
* 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;
}
2 голосов
/ 30 сентября 2015

Попробуйте 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 голосов
/ 06 марта 2010

Я в конце концов выбрал это решение, которое работает для суммирования положительных и отрицательных целых чисел (и с небольшим изменением регулярного выражения будет работать и для десятичных дробей):

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...