cqp 1 settimana fa
parent
commit
a022251c13
1 ha cambiato i file con 75 aggiunte e 45 eliminazioni
  1. 75 45
      app/Service/Service.php

+ 75 - 45
app/Service/Service.php

@@ -787,89 +787,122 @@ class Service
         return $this->evaluateWithBCMath($expr, $decimals);
     }
 
-
-    /**
-     * 用 bcmath 安全执行表达式 (支持 + - * /)
-     */
-    public function evaluateWithBCMath1($expr, $decimals = 2)
+    public function evaluateWithBCMath2($expr, $decimals = 2)
     {
-//        preg_match_all('/(\d+(?:\.\d+)?|[+\/*-])/', $expr, $matches);
-        preg_match_all('/(-?\d+(?:\.\d+)?|[+\/*-])/', $expr, $matches);
+        $calcDecimals = 8; // 中间计算用更高精度
+        // 支持负数和小数
+        preg_match_all('/-?\d+(?:\.\d+)?|[+\/*-]/', $expr, $matches);
         $tokens = $matches[0] ?? [];
 
-        if (empty($tokens)) return 0;
+        if (empty($tokens)) return '0';
 
+        // Shunting-yard 算法转换为逆波兰表达式 (RPN)
         $output = [];
         $ops = [];
+        $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
 
         foreach ($tokens as $token) {
             if (is_numeric($token)) {
                 $output[] = (string)$token;
-            } elseif (in_array($token, ['+', '-', '*', '/'])) {
+            } elseif (isset($precedence[$token])) {
+                while (!empty($ops) && $precedence[end($ops)] >= $precedence[$token]) {
+                    $output[] = array_pop($ops);
+                }
                 $ops[] = $token;
             }
         }
+        while (!empty($ops)) {
+            $output[] = array_pop($ops);
+        }
 
-        $result = array_shift($output);
-        foreach ($ops as $i => $op) {
-            $next = $output[$i] ?? '0';
+        // 计算 RPN
+        $stack = [];
+        foreach ($output as $token) {
+            if (is_numeric($token)) {
+                $stack[] = (string)$token;
+            } else {
+                $b = array_pop($stack) ?? '0';
+                $a = array_pop($stack) ?? '0';
 
-            switch ($op) {
-                case '+':
-                    $result = bcadd($result, $next, $decimals);
-                    break;
-                case '-':
-                    $result = bcsub($result, $next, $decimals);
-                    break;
-                case '*':
-                    $result = bcmul($result, $next, $decimals);
-                    break;
-                case '/':
-                    if (bccomp($next, '0', $decimals) === 0) {
-                        $result = '0'; // 避免除零
-                    } else {
-                        $result = bcdiv($result, $next, $decimals);
-                    }
-                    break;
+                switch ($token) {
+                    case '+':
+                        $stack[] = bcadd($a, $b, $calcDecimals);
+                        break;
+                    case '-':
+                        $stack[] = bcsub($a, $b, $calcDecimals);
+                        break;
+                    case '*':
+                        $stack[] = bcmul($a, $b, $calcDecimals);
+                        break;
+                    case '/':
+                        if (bccomp($b, '0', $calcDecimals) === 0) {
+                            $stack[] = '0';
+                        } else {
+                            $stack[] = bcdiv($a, $b, $calcDecimals);
+                        }
+                        break;
+                }
             }
         }
 
-        return $result;
+        $result = array_pop($stack) ?? '0';
+        // 最终统一格式化为指定小数位
+        return bcadd($result, '0', $decimals);
     }
 
     public function evaluateWithBCMath($expr, $decimals = 2)
     {
-        $calcDecimals = 8; // 中间计算用更高精度
-        // 支持负数和小数
-        preg_match_all('/-?\d+(?:\.\d+)?|[+\/*-]/', $expr, $matches);
+        $calcDecimals = 8; // 中间计算精度更高
+
+        // ✅ 使用 ~ 作为正则分隔符,避免与除号 / 冲突
+        preg_match_all('~-?\d+(?:\.\d+)?|[+\-*/()]~', $expr, $matches);
         $tokens = $matches[0] ?? [];
 
         if (empty($tokens)) return '0';
 
-        // Shunting-yard 算法转换为逆波兰表达式 (RPN)
+        // 运算符优先级
+        $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
+
+        // === Shunting Yard 算法 ===
         $output = [];
         $ops = [];
-        $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
+        $prevToken = null;
 
         foreach ($tokens as $token) {
             if (is_numeric($token)) {
-                $output[] = (string)$token;
+                $output[] = $token;
+            } elseif ($token === '(') {
+                $ops[] = $token;
+            } elseif ($token === ')') {
+                while (!empty($ops) && end($ops) !== '(') {
+                    $output[] = array_pop($ops);
+                }
+                array_pop($ops); // 弹出 (
             } elseif (isset($precedence[$token])) {
-                while (!empty($ops) && $precedence[end($ops)] >= $precedence[$token]) {
+                // 一元负号处理
+                if ($token === '-' && ($prevToken === null || $prevToken === '(' || isset($precedence[$prevToken]))) {
+                    $output[] = '0';
+                }
+
+                while (!empty($ops) && isset($precedence[end($ops)]) && $precedence[end($ops)] >= $precedence[$token]) {
                     $output[] = array_pop($ops);
                 }
+
                 $ops[] = $token;
             }
+
+            $prevToken = $token;
         }
+
         while (!empty($ops)) {
             $output[] = array_pop($ops);
         }
 
-        // 计算 RPN
+        // === 计算逆波兰表达式 ===
         $stack = [];
         foreach ($output as $token) {
             if (is_numeric($token)) {
-                $stack[] = (string)$token;
+                $stack[] = $token;
             } else {
                 $b = array_pop($stack) ?? '0';
                 $a = array_pop($stack) ?? '0';
@@ -885,18 +918,15 @@ class Service
                         $stack[] = bcmul($a, $b, $calcDecimals);
                         break;
                     case '/':
-                        if (bccomp($b, '0', $calcDecimals) === 0) {
-                            $stack[] = '0';
-                        } else {
-                            $stack[] = bcdiv($a, $b, $calcDecimals);
-                        }
+                        $stack[] = (bccomp($b, '0', $calcDecimals) === 0)
+                            ? '0'
+                            : bcdiv($a, $b, $calcDecimals);
                         break;
                 }
             }
         }
 
         $result = array_pop($stack) ?? '0';
-        // 最终统一格式化为指定小数位
         return bcadd($result, '0', $decimals);
     }
 }