web-dev-qa-db-fra.com

Comment évaluer mathématiquement une chaîne telle que "2-1" pour produire "1"?

Je me demandais juste si PHP avait une fonction qui peut prendre une chaîne comme 2-1 et en produire le résultat arithmétique?

Ou devrais-je le faire manuellement avec explode() pour obtenir les valeurs gauche et droite de l'opérateur arithmétique?

25
Nikola

Je sais que cette question est ancienne, mais je l’ai trouvée hier soir en cherchant quelque chose qui n’était pas tout à fait en rapport, et chaque réponse ici est mauvaise. Pas seulement mauvais, très mauvais. Les exemples que je donne ici seront tirés d'une classe que j'ai créée en 2005 et que j'ai passé les dernières heures à mettre à jour pour PHP5 à cause de cette question. D'autres systèmes existaient et existaient avant que cette question ne soit posée. Je ne comprends donc pas pourquoi chaque réponse vous dit d'utiliser eval , alors que la mise en garde de PHP est la suivante:

La construction de langage eval () est très dangereuse car elle permet l'exécution de code PHP arbitraire. Son utilisation est donc découragée. Si vous avez soigneusement vérifié qu’il n’y avait pas d’autre option que d’utiliser cette construction, faites particulièrement attention à ne pas y transmettre de données fournies par les utilisateurs sans la valider correctement à l’avance. 

Avant de passer à l’exemple, les endroits où obtenir le cours que je vais utiliser se trouvent sur PHPClasses ou GitHub . eos.class.php et stack.class.php sont obligatoires, mais peuvent être combinés dans le même fichier.

La raison d'utiliser une classe comme celle-ci est qu'elle inclut un analyseur RPN (infixe dans un pseudo-post-correctif), puis un solveur RPN. Avec ceux-ci, vous ne devez jamais utiliser la fonction eval et ouvrir votre système aux vulnérabilités. Une fois que vous avez les classes, le code suivant est tout ce qui est nécessaire pour résoudre une équation simple (à plus complexe) telle que votre exemple 2-1.

require_once "eos.class.php";
$equation = "2-1";
$eq = new eqEOS();
$result = $eq->solveIF($equation);

C'est tout! Vous pouvez utiliser un analyseur comme celui-ci pour la plupart des équations, aussi compliquées soient-elles, sans jamais avoir à recourir à la méthode 'evil eval'.

Parce que je ne veux vraiment pas seulement que mon cours soit inclus, voici quelques autres options. Je connais juste le mien car je l'utilise depuis 8 ans. ^^

API Wolfram | Alpha
Sauge
Un assez mauvais analyseur
phpdicecalc

Je ne savais pas trop ce qui était arrivé aux autres personnes que j'avais découvertes précédemment - j'en avais déjà rencontré une autre sur GitHub. Malheureusement, je ne l'avais pas ajouté aux signets, mais cela était lié à de grandes opérations de flottaison incluant un analyseur.

Quoi qu'il en soit, je voulais m'assurer qu'une réponse à la résolution des équations dans PHP ne pointait pas tous les futurs chercheurs sur eval, car cela se trouvait en haut d'une recherche google. ^^

51
Jon
$operation='2-1';
eval("\$value = \"$operation\";");

ou

$value=eval("return ($op);");
14
dynamic

C’est l’un des cas où eval est pratique: 

$expression = '2 - 1';
eval( '$result = (' . $expression . ');' );
echo $result;
8
gion_13

Vous pouvez utiliser la précision arbitraire de BC Math

echo bcsub(5, 4); // 1
echo bcsub(1.234, 5); // 3
echo bcsub(1.234, 5, 4); // -3.7660

http://www.php.net/manual/en/function.bcsub.php

7
delphist

Dans this forum, quelqu'un l'a fait sans eval. Peut-être que vous pouvez l'essayer? Crédits à eux, je viens de le trouver. 

function calculate_string( $mathString )    {
    $mathString = trim($mathString);     // trim white spaces
    $mathString = ereg_replace ('[^0-9\+-\*\/\(\) ]', '', $mathString);    // remove any non-numbers chars; exception for math operators

    $compute = create_function("", "return (" . $mathString . ");" );
    return 0 + $compute();
}

$string = " (1 + 1) * (2 + 2)";
echo calculate_string($string);  // outputs 8  
3
AmirG

Voir également cette réponse ici: Évaluation d'une chaîne d'expressions mathématiques simples

Veuillez noter que cette solution n'est PAS conforme à BODMAS, mais vous pouvez utiliser des crochets dans votre chaîne d'évaluation pour résoudre ce problème.

function callback1($m) {
    return string_to_math($m[1]);
}
function callback2($n,$m) {
    $o=$m[0];
    $m[0]=' ';
    return $o=='+' ? $n+$m : ($o=='-' ? $n-$m : ($o=='*' ? $n*$m : $n/$m));
}
function string_to_math($s){ 
    while ($s != ($t = preg_replace_callback('/\(([^()]*)\)/','callback1',$s))) $s=$t;
    preg_match_all('![-+/*].*?[\d.]+!', "+$s", $m);
    return array_reduce($m[0], 'callback2');
}
echo string_to_match('2-1'); //returns 1
2
kurdtpage

Voici un morceau de code quelque peu verbeux que j'ai lancé pour une autre question SO . Il est conforme à BOMDAS sans eval(), mais n'est pas équipé pour faire des expressions complexes/d'ordre supérieur/entre parenthèses. Cette approche sans bibliothèque sépare l'expression et réduit systématiquement le tableau de composants jusqu'à ce que tous les opérateurs soient supprimés. Cela fonctionne certainement pour votre exemple d'expression: 2-1;)

  1. preg_match() vérifie que chaque opérateur a une sous-chaîne numérique de chaque côté.
  2. preg_split() divise la chaîne en un tableau de nombres et d'opérateurs en alternance.
  3. array_search() trouve l'index de l'opérateur ciblé, alors qu'il existe dans le tableau.
  4. array_splice() remplace l'élément d'opérateur et les éléments situés de chaque côté par un nouvel élément contenant le résultat mathématique des trois éléments supprimés.

** mis à jour pour autoriser les nombres négatifs **

Code: ( Démo )

$expression="-11+3*1*4/-6-12";
if(!preg_match('~^-?\d*\.?\d+([*/+-]-?\d*\.?\d+)*$~',$expression)){
    echo "invalid expression";
}else{
    $components=preg_split('~(?<=\d)([*/+-])~',$expression,NULL,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    var_export($components);  // ['-11','+','3','*','1','*','4','/','-6','-','12']
    while(($index=array_search('*',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]*$components[$index+1]);
        var_export($components);
        // ['-11','+','3','*','4','/','-6','-','12']
        // ['-11','+','12','/','-6','-','12']
    }
    while(($index=array_search('/',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]/$components[$index+1]);
        var_export($components);  // [-'11','+','-2','-','12']
    }
    while(($index=array_search('+',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]+$components[$index+1]);
        var_export($components);  // ['-13','-','12']
    }
    while(($index=array_search('-',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]-$components[$index+1]);
        var_export($components); // [-25]
    }
    echo current($components);  // -25
}

Voici une démo du BVersion OMDAS qui utilise pow() de php lorsque ^ est rencontré entre deux nombres (positif ou négatif).

Je ne pense pas que je vais jamais avoir la peine d'écrire une version qui gère les expressions entre parenthèses ... mais nous verrons à quel point je m'ennuie.

1
mickmackusa

Comme create_function est devenu obsolète et qu'il me fallait absolument une solution légère alternative pour évaluer string en maths. Après quelques heures de dépenses, je me suis mis à suivre. Au fait, je ne me souciais pas des parenthèses car je n'en ai pas besoin dans mon cas. J'avais juste besoin de quelque chose qui conforme la priorité de l'opérateur correctement. 

Mise à jour: J'ai également ajouté le support entre parenthèses. Veuillez vérifier ce projet Évaluer Math String

function evalAsMath($str) {

   $error = false;
   $div_mul = false;
   $add_sub = false;
   $result = 0;

   $str = preg_replace('/[^\d\.\+\-\*\/]/i','',$str);
   $str = rtrim(trim($str, '/*+'),'-');

   if ((strpos($str, '/') !== false ||  strpos($str, '*') !== false)) {
      $div_mul = true;
      $operators = array('*','/');
      while(!$error && $operators) {
         $operator = array_pop($operators);
         while($operator && strpos($str, $operator) !== false) {
           if ($error) {
              break;
            }
           $regex = '/([\d\.]+)\\'.$operator.'(\-?[\d\.]+)/';
           preg_match($regex, $str, $matches);
           if (isset($matches[1]) && isset($matches[2])) {
                if ($operator=='+') $result = (float)$matches[1] + (float)$matches[2];
                if ($operator=='-') $result = (float)$matches[1] - (float)$matches[2]; 
                if ($operator=='*') $result = (float)$matches[1] * (float)$matches[2]; 
                if ($operator=='/') {
                   if ((float)$matches[2]) {
                      $result = (float)$matches[1] / (float)$matches[2];
                   } else {
                      $error = true;
                   }
                }
                $str = preg_replace($regex, $result, $str, 1);
                $str = str_replace(array('++','--','-+','+-'), array('+','+','-','-'), $str);
         } else {
            $error = true;
         }
      }
    }
}

  if (!$error && (strpos($str, '+') !== false ||  strpos($str, '-') !== false)) {
     $add_sub = true;
     preg_match_all('/([\d\.]+|[\+\-])/', $str, $matches);
     if (isset($matches[0])) {
         $result = 0;
         $operator = '+';
         $tokens = $matches[0];
         $count = count($tokens);
         for ($i=0; $i < $count; $i++) { 
             if ($tokens[$i] == '+' || $tokens[$i] == '-') {
                $operator = $tokens[$i];
             } else {
                $result = ($operator == '+') ? ($result + (float)$tokens[$i]) : ($result - (float)$tokens[$i]);
             }
         }
      }
    }

    if (!$error && !$div_mul && !$add_sub) {
       $result = (float)$str;
    }
    return $error ? 0 : $result;
}

Démo: http://sandbox.onlinephpfunctions.com/code/fdffa9652b748ac8c6887d91f9b10fe62366c650

0
Samir