|
@@ -787,89 +787,122 @@ class Service
|
|
return $this->evaluateWithBCMath($expr, $decimals);
|
|
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] ?? [];
|
|
$tokens = $matches[0] ?? [];
|
|
|
|
|
|
- if (empty($tokens)) return 0;
|
|
|
|
|
|
+ if (empty($tokens)) return '0';
|
|
|
|
|
|
|
|
+ // Shunting-yard 算法转换为逆波兰表达式 (RPN)
|
|
$output = [];
|
|
$output = [];
|
|
$ops = [];
|
|
$ops = [];
|
|
|
|
+ $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
|
|
|
|
|
|
foreach ($tokens as $token) {
|
|
foreach ($tokens as $token) {
|
|
if (is_numeric($token)) {
|
|
if (is_numeric($token)) {
|
|
$output[] = (string)$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;
|
|
$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)
|
|
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] ?? [];
|
|
$tokens = $matches[0] ?? [];
|
|
|
|
|
|
if (empty($tokens)) return '0';
|
|
if (empty($tokens)) return '0';
|
|
|
|
|
|
- // Shunting-yard 算法转换为逆波兰表达式 (RPN)
|
|
|
|
|
|
+ // 运算符优先级
|
|
|
|
+ $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
|
|
|
|
+
|
|
|
|
+ // === Shunting Yard 算法 ===
|
|
$output = [];
|
|
$output = [];
|
|
$ops = [];
|
|
$ops = [];
|
|
- $precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
|
|
|
|
|
|
+ $prevToken = null;
|
|
|
|
|
|
foreach ($tokens as $token) {
|
|
foreach ($tokens as $token) {
|
|
if (is_numeric($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])) {
|
|
} 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);
|
|
$output[] = array_pop($ops);
|
|
}
|
|
}
|
|
|
|
+
|
|
$ops[] = $token;
|
|
$ops[] = $token;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ $prevToken = $token;
|
|
}
|
|
}
|
|
|
|
+
|
|
while (!empty($ops)) {
|
|
while (!empty($ops)) {
|
|
$output[] = array_pop($ops);
|
|
$output[] = array_pop($ops);
|
|
}
|
|
}
|
|
|
|
|
|
- // 计算 RPN
|
|
|
|
|
|
+ // === 计算逆波兰表达式 ===
|
|
$stack = [];
|
|
$stack = [];
|
|
foreach ($output as $token) {
|
|
foreach ($output as $token) {
|
|
if (is_numeric($token)) {
|
|
if (is_numeric($token)) {
|
|
- $stack[] = (string)$token;
|
|
|
|
|
|
+ $stack[] = $token;
|
|
} else {
|
|
} else {
|
|
$b = array_pop($stack) ?? '0';
|
|
$b = array_pop($stack) ?? '0';
|
|
$a = array_pop($stack) ?? '0';
|
|
$a = array_pop($stack) ?? '0';
|
|
@@ -885,18 +918,15 @@ class Service
|
|
$stack[] = bcmul($a, $b, $calcDecimals);
|
|
$stack[] = bcmul($a, $b, $calcDecimals);
|
|
break;
|
|
break;
|
|
case '/':
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
$result = array_pop($stack) ?? '0';
|
|
$result = array_pop($stack) ?? '0';
|
|
- // 最终统一格式化为指定小数位
|
|
|
|
return bcadd($result, '0', $decimals);
|
|
return bcadd($result, '0', $decimals);
|
|
}
|
|
}
|
|
}
|
|
}
|