
本文介绍一种基于运算符优先级的表达式解析方案,解决原始代码中因未处理运算顺序导致的计算错误问题,支持形如 `5^; 1000 + 6^ - 5^ + 1` 的输入,并准确输出 `25 1012`。
原始实现的问题核心在于:它采用线性左结合扫描方式(类似 ((a op1 b) op2 c) ...),完全忽略了数学中的运算符优先级(如 + 和 - 优先级低于 *、/,而 ^ 在本题中是后缀一元操作符,需立即作用于前一个数),更未正确识别 X^ 这一特殊语法——它不是二元幂运算(如 2^3),而是后缀平方标记,仅表示“将前面的数字自乘”。
此外,原始代码中 tokens[i] === "^" && tokens[i + 1] === "" 的判断逻辑存在根本缺陷:正则分割 /(\+|\-|\*|\/|\^)/g 会把 6^ 拆成 ["6", "^", ""],但 "" 并非可靠标识;且 result = Math.pow(result, 2) 错误地将整个中间结果平方,而非仅对 6 或 5 等独立数字平方。
✅ 推荐采用分阶段预处理 + 优先级驱动求值策略,清晰、健壮、易维护:
步骤一:预处理 —— 将 X^ 转换为 X * X
利用正则全局替换,安全展开所有平方标记:
function expandSquares(input) {
return input.replace(/(\d+)\^/g, '$1 * $1');
}
// 示例:expandSquares("1000 + 6^ - 5^ + 1") → "1000 + 6 * 6 - 5 * 5 + 1"步骤二:按优先级分步求值
先处理 * 和 /(高优先级),再处理 + 和 -(低优先级)。每轮遍历,就地简化最左侧可计算的子表达式:
function evaluateMultiplicationDivision(expr) {
let tokens = expr.split(/([+\-*/])/).filter(t => t.trim() !== '');
for (let i = 1; i < tokens.length; i += 2) {
const op = tokens[i];
if (op === '*' || op === '/') {
const left = parseFloat(tokens[i - 1]);
const right = parseFloat(tokens[i + 1]);
const result = op === '*' ? left * right : left / right;
// 替换三个元素为结果
tokens.splice(i - 1, 3, result.toString());
i -= 2; // 重置索引,处理新生成的token
}
}
return tokens.join('');
}
function evaluateAdditionSubtraction(expr) {
let tokens = expr.split(/([+\-])/).filter(t => t.trim() !== '');
// 确保首项为正数(处理开头的负号)
if (tokens[0] === '-') {
tokens.splice(0, 2, '-' + tokens[1]);
}
for (let i = 1; i < tokens.length; i += 2) {
const op = tokens[i];
if (op === '+' || op === '-') {
const left = parseFloat(tokens[i - 1]);
const right = parseFloat(tokens[i + 1]);
const result = op === '+' ? left + right : left - right;
tokens.splice(i - 1, 3, result.toString());
i -= 2;
}
}
return tokens[0]; // 唯一剩余值即结果
}步骤三:组合主函数
function calculateOne(expression) {
try {
const expanded = expandSquares(expression.trim());
const noMD = evaluateMultiplicationDivision(expanded);
const result = evaluateAdditionSubtraction(noMD);
return parseFloat(result).toString();
} catch (e) {
return "Error";
}
}
function calculate() {
const input = document.getElementById("input").value;
const expressions = input.split(';').map(e => e.trim()).filter(e => e);
const results = expressions.map(calculateOne);
document.getElementById("result").innerHTML = results.join(' ') + '
';
}✅ 验证示例
输入:5^; 1000 + 6^ - 5^ + 1
- 5^ → 5 * 5 → 25
- 1000 + 6^ - 5^ + 1 → 1000 + 6 * 6 - 5 * 5 + 1
→ 先算 6*6=36, 5*5=25 → 1000 + 36 - 25 + 1
→ 再算 1000+36=1036, 1036−25=1011, 1011+1=1012
输出:25 1012 —— 完全符合预期。
⚠️ 注意事项:
- 本方案假设输入仅含整数、+ - * / 和 X^,不支持括号或浮点数;如需增强,可引入递归下降解析器。
- parseFloat 可处理带空格的数字,但生产环境建议添加输入校验(如 /^\s*\d+\s*\^\s*$/ 匹配合法 X^)。
- split(/([+\-*/])/) 保留运算符,确保结构清晰;避免使用 parseInt(会截断小数)。
该方法摒弃了脆弱的 token 索引推演,转而用语义明确的字符串变换与分层规约,大幅提升可读性与鲁棒性。










