web-dev-qa-db-fra.com

Évaluer une chaîne en tant qu'expression mathématique en JavaScript

Comment analyser et évaluer une expression mathématique dans une chaîne (par exemple, '1+1') sans appeler eval(string) pour obtenir sa valeur numérique?

Avec cet exemple, je veux que la fonction accepte '1+1' et renvoie 2.

53
wheresrhys

J'ai finalement opté pour cette solution, qui fonctionne pour additionner les entiers positifs et négatifs (et avec une petite modification de la regex fonctionnera également pour les nombres décimaux):

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

Je ne sais pas si c'est plus rapide que eval (), mais comme je dois effectuer l'opération plusieurs fois, je suis beaucoup plus à l'aise avec l'exécution de ce script que de créer des charges d'instances du compilateur javascript.

2
wheresrhys

Vous pouvez utiliser la bibliothèque JavaScript Expression Evaluator , qui vous permet d'effectuer des tâches telles que: 

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

Ou mathjs , qui permet des choses comme:

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

J'ai fini par choisir mathjs pour l'un de mes projets.

52
Rafael Vega

// Vous pouvez faire + ou - facilement:

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)

Des mathématiques plus complexes rendent eval plus attrayant et certainement plus simple à écrire.

18
kennebec

Quelqu'un doit analyser cette chaîne. Si ce n'est pas l'interprète (via eval), il vous faudra alors écrire une routine d'analyse pour extraire des nombres, des opérateurs et tout ce que vous souhaitez utiliser dans une expression mathématique.

Donc, non, il n'y a pas de moyen (simple) sans eval. Si la sécurité vous préoccupe (parce que l'entrée que vous analysez ne provient pas d'une source que vous contrôlez), vous pouvez peut-être vérifier le format de l'entrée (via un filtre regex de liste blanche) avant de le transmettre à eval?

17
bdukes

Je suis allé à la recherche de bibliothèques JavaScript pour évaluer les expressions mathématiques et ai trouvé ces deux candidats prometteurs:

  • JavaScript Expression Evaluator : Plus petit et, espérons-le, plus Léger. Permet des expressions algébriques, des substitutions et un nombre de fonctions

  • mathjs : Permet également les nombres complexes, les matrices et les unités . Conçu pour être utilisé à la fois par JavaScript dans le navigateur et par Node.js.

7
Itangalo

J'ai récemment fait cela en C # (pas de Eval () pour nous ...) en évaluant l'expression dans Reverse Polish Notation (c'est le bit facile). La partie difficile est en fait d'analyser cette chaîne et de la transformer en notation polonaise inversée. J'ai utilisé l'algorithme Shunting Yard car il existe un excellent exemple sur Wikipedia et le pseudocode. J'ai trouvé cela très simple d'implémenter les deux et je le recommanderais si vous n'avez pas déjà trouvé de solution ou si vous cherchez des alternatives.

6
RichK

J'ai créé BigEval dans le même but.
En résolvant les expressions, il fonctionne exactement comme Eval() et supporte des opérateurs comme%, ^, &, ** (puissance) et! (factorielle) . Vous êtes également autorisé à utiliser des fonctions et des constantes (ou des variables) à l'intérieur de l'expression. L'expression est résolue dans ordre PEMDAS , ce qui est courant dans les langages de programmation, y compris 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

Il est également possible d'utiliser ces bibliothèques de grands nombres pour l'arithmétique au cas où vous traiteriez des nombres avec une précision arbitraire.

6
Avi

C’est une petite fonction que j’ai réunie tout à l’heure pour résoudre ce problème: elle construit l’expression en analysant la chaîne caractère par caractère (c’est en fait assez rapide). Cela prendra n'importe quelle expression mathématique (limitée à +, -, *,/opérateurs seulement) et retournera le résultat. Il peut également gérer des valeurs négatives et des opérations à nombre illimité.

La seule chose à faire est de s’assurer qu’il calcule * &/avant + & -. Ajoutera cette fonctionnalité ultérieurement, mais pour l'instant, c'est ce dont j'ai besoin ...

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

Une alternative à l'excellente réponse de @kennebec, utilisant une expression régulière plus courte et laissant des espaces entre opérateurs 

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

Utilisez-le comme

addbits('5 + 30 - 25.1 + 11');

Mettre à jour

Voici une version plus optimisée

function addbits(s) {
    return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
        .reduce(function(sum, value) {
            return parseFloat(sum) + parseFloat(value);
        });
}
4
Stefan Gabos

Essayez 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

Essayez AutoCalculator https://github.com/JavscriptLab/autocalculate Calculer les valeurs d'entrée et de sortie à l'aide d'expressions de sélecteur

Ajoutez simplement un attribut pour votre entrée en sortie, tel que Data-ac = "(# firstinput + # secondinput)"

Pas besoin d'initialisation, ajoutez simplement l'attribut data-ac ... .. Il découvrira automatiquement les éléments ajoutés dynamiquement

FOr ajouter 'Rs' avec Output, ajouter juste entre accolades Data-ac = "{Rs} (# firstinput + # secondinput)"

1
Justin Jose

Je crois que parseInt et ES6 peuvent être utiles dans cette situation 

==> / de cette façon: 

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

La chose principale ici est que parseInt analyse le numéro avec l'opérateur. Le code peut être modifié pour les besoins correspondants.

1
fuser

Voici une solution algorithmique similaire à celle de jMichael qui parcourt l'expression, caractère par caractère, et trace progressivement gauche/opérateur/droite. La fonction accumule le résultat après chaque tour où elle trouve un caractère d'opérateur. Cette version ne supporte que les opérateurs '+' et '-' mais est écrite pour être étendue à d'autres opérateurs. Remarque: nous plaçons 'currOp' sur '+' avant la mise en boucle car nous supposons que l'expression commence par un float positif. En fait, dans l’ensemble, je pars du principe que l’intrant est similaire à ce qui proviendrait d’une calculatrice. 

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

l'addition est 2.

0
Rushikesh Bharad