it-roy-ru.com

Оценка строки как математического выражения в JavaScript

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

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

53
wheresrhys

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

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.

2
wheresrhys

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

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

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

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

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

52
Rafael Vega

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

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

18
kennebec

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

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

17
bdukes

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

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

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

7
Itangalo

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

6
RichK

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

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

6
Avi

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

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

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

Альтернатива отличному ответу @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);
        });
}
4
Stefan Gabos

Попробуйте 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
ArchHaskeller

Попробуйте AutoCalculator https://github.com/JavscriptLab/autocalculate Рассчитать входные значения и выходные данные с помощью выражений селектора

Просто добавьте атрибут для вашего выходного ввода, например Data-ac = "(# firstinput + # secondinput)"

Нет необходимости в какой-либо инициализации, просто добавьте только атрибут data-ac . Он автоматически обнаружит динамически добавленные элементы

Для добавления 'Rs' с помощью Output просто добавьте фигурную скобку Data-ac = "{Rs} (# firstinput + # secondinput)"

1
Justin Jose

Я считаю, что parseInt и ES6 могут быть полезны в этой ситуации 

==> таким образом: 

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

Главное, что parseInt анализирует номер с оператором. Код может быть изменен для соответствующих нужд.

1
fuser

Вот алгоритмическое решение, подобное jMichael's, которое перебирает выражение символ за символом и последовательно отслеживает left/operator/right. Функция накапливает результат после каждого хода, она находит символ оператора. Эта версия поддерживает только операторы «+» и «-», но написана для расширения с другими операторами. Примечание: мы устанавливаем '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);
}
0
internetross
const getAddition = (str) => {
  return str.split('+').reduce((total, num) => (total + num * 1), 0);
};

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

дополнение 2.

0
Rushikesh Bharad