如何正确解析含平方符号的多表达式计算器

本文介绍一种健壮的多表达式计算器实现方案,通过预处理平方标记(如 `5^` → `5 * 5`)、分阶段执行乘除与加减运算,解决因忽略运算优先级和 token 解析错误导致的计算结果异常问题。

原始代码存在多个关键缺陷:

  • 平方符号解析逻辑错误:tokens[i] === "^" && tokens[i + 1] === "" 的判断无法可靠识别 X^ 结构(因正则分割后 ^ 后可能为空字符串,但上下文易错乱);
  • 未遵循运算优先级:直接线性遍历 token 并逐个应用 +, -, *, /, ^,等价于从左到右强制计算(如 1000 + 6^ - 5^ + 1 被误为 (((1000 + 6)^) - 5)^ + 1),而非先算平方、再算乘除、最后加减;
  • output 累加位置错误:在内层循环中反复拼接 result,导致中间状态被多次输出(例如 1000 + 6^ 阶段就输出一次 1036,后续操作继续叠加,造成 1024206744962 这类荒谬值);
  • 类型混淆与隐式转换风险:parseInt 在非数字字符上返回 NaN,且未做容错校验。

✅ 推荐采用「预处理 + 分阶段求值」策略,清晰分离关注点:

步骤一:标准化平方语法

将所有 X^ 替换为 X * X,使表达式完全兼容标准四则运算解析器:

const expandSquares = (expr) => {
  return expr.replaceAll(/(\d+)\^/g, '$1 * $1');
};
// 示例:expandSquares("1000 + 6^ - 5^ + 1") → "1000 + 6 * 6 - 5 * 5 + 1"

步骤二:实现两级运算求值(无括号版)

先处理 * 和 /,再处理 + 和 -,确保优先级正确:

const evaluate = (expr) => {
  // Step 1: 处理乘除(从左到右)
  let tokens = expr.split(/([+\-*/])/).filter(t => t.trim());
  for (let i = 1; i < tokens.length; i += 2) {
    if (tokens[i] === '*' || tokens[i] === '/') {
      const left = parseFloat(tokens[i - 1]);
      const right = parseFloat(tokens[i + 1]);
      if (isNaN(left) || isNaN(right)) throw new Error(`Invalid number in: ${expr}`);
      const res = tokens[i] === '*' ? left * right : left / right;
      // 替换三元组为结果
      tokens.splice(i - 1, 3, res.toString());
      i -= 2; // 重置索引以处理新生成的 token
    }
  }

  // Step 2: 处理加减(从左到右)
  let result = parseFloat(tokens[0]);
  for (let i = 1; i < tokens.length; i += 2) {
    if (tokens[i] === '+') {
      result += parseFloat(tokens[i + 1]);
    } else if (tokens[i] === '-') {
      result -= parseFloat(tokens[i + 1]);
    }
  }
  return result;
};

步骤三:整合主逻辑

完整支持分号分隔的多表达式输入:

function calculate() {
  const input = document.getElementById("input").value.trim();
  if (!input) return;

  const expressions = input.split(';').map(e => e.trim()).filter(Boolean);
  const results = expressions.map(expr => {
    try {
      const normalized = expandSquares(expr);
      return evaluate(normalized);
    } catch (e) {
      return 'Error';
    }
  });

  document.getElementById("result").innerHTML = results.join(' ') + '
'; }

✅ 验证示例

输入:5^; 1000 + 6^ - 5^ + 1
→ 展开为:5 * 5; 1000 + 6 * 6 - 5 * 5 + 1
→ 计算得:25; 1000 + 36 - 25 + 1 = 1012
→ 输出:25 1012 ✔️

⚠️ 注意事项

  • 本方案暂不支持括号或负数(如 -5^

    ),如需扩展,应在 expandSquares 中增强正则(如 /-?\d+\^/g)并完善 evaluate 的一元运算处理;
  • 实际生产环境建议使用成熟库(如 mathjs)或构建抽象语法树(AST),但本教程方案在教学与轻量需求下简洁高效、易于理解与调试;
  • 始终对 parseFloat 结果做 isNaN 检查,避免静默失败。

通过结构化预处理与分阶段计算,你将彻底规避线性解析带来的优先级混乱,让计算器输出既准确又可预测。