You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Shunting Yard算法的科学计算器开发技术咨询

科学计算器问题解决方案

问题1:相同优先级运算未遵循从左到右顺序

问题原因

当前实现的hasPrecedence方法仅区分了乘除与加减的优先级差异,未处理相同优先级运算符的左结合规则;同时运算符入栈逻辑仅弹出一次高优先级运算符,未循环处理所有符合条件的栈顶运算符,导致相同优先级运算顺序颠倒(如8/2/2会计算为8/(2/2)=8,而非正确的(8/2)/2=2)。

修复方案

  1. 新增优先级判断方法,明确各运算符的优先级等级
  2. 修改hasPrecedence方法,支持左结合规则(相同优先级时,栈顶运算符先执行)
  3. 调整运算符入栈逻辑,循环弹出所有优先级高于或等于当前运算符的栈顶元素

修改后的关键代码

// 新增:获取运算符优先级
private static int getPrecedence(char op) {
    return switch(op) {
        case '+', '-' -> 1;
        case 'x', '/' -> 2;
        case '(' -> 0;
        default -> -1;
    };
}

// 新增:判断运算符是否为左结合
private static boolean isLeftAssociative(char op) {
    return op == '+' || op == '-' || op == 'x' || op == '/';
}

// 修改:优先级判断方法
public static boolean hasPrecedence(char op1, char op2) {
    int p1 = getPrecedence(op1);
    int p2 = getPrecedence(op2);
    // 左结合:优先级相同则栈顶运算符先执行
    return p1 > p2 || (p1 == p2 && isLeftAssociative(op1));
}

// 修改:运算符入栈逻辑
else if (tokens[i] == 'x' || tokens[i] == '-' || tokens[i] == '/' || tokens[i] == '+') {
    char currentOp = tokens[i];
    // 循环弹出所有符合优先级条件的栈顶运算符
    while (!ops.isEmpty() && ops.peek() != '(' && hasPrecedence((char) ops.peek(), currentOp)) {
        values.add(ops.pop());
    }
    ops.push(currentOp);
}

问题2:科学函数的集成与优先级遵循

问题原因

当前基于单个字符的匹配方式无法识别多字符函数名,且未将科学函数纳入BODMAS优先级体系(函数优先级应高于乘除),同时缺少函数调用的计算逻辑。

解决方案

  1. 通过连续字母收集函数名,替代逐个字符匹配
  2. 将函数视为高优先级的前缀运算符,调整Shunting Yard算法的优先级判断与栈处理逻辑
  3. 新增函数计算方法,在后缀表达式求值时处理函数调用

修改后的关键代码

首先,将栈和队列泛型化以支持字符串类型的函数名:

Queue<Object> values = new LinkedList<>();
Stack<Object> ops = new Stack<>();

然后添加函数识别逻辑:

// 在数字处理分支后添加函数识别
else if (Character.isLetter(tokens[i])) {
    StringBuffer funcBuf = new StringBuffer();
    // 收集连续字母作为函数名
    while (i < tokens.length && Character.isLetter(tokens[i])) {
        funcBuf.append(tokens[i]);
        i++;
    }
    i--; // 修正循环后索引偏移
    ops.push(funcBuf.toString());
}

修改优先级判断方法以支持函数:

private static int getPrecedence(Object op) {
    if (op instanceof Character) {
        char c = (Character) op;
        return switch(c) {
            case '+', '-' -> 1;
            case 'x', '/' -> 2;
            case '(' -> 0;
            default -> -1;
        };
    } else if (op instanceof String) {
        // 科学函数优先级高于乘除
        return 3;
    }
    return -1;
}

public static boolean hasPrecedence(Object op1, Object op2) {
    int p1 = getPrecedence(op1);
    int p2 = getPrecedence(op2);
    // 函数为右结合(如sin(cos(x))先算cos),优先级相同时不弹出栈顶函数
    if (op1 instanceof String) {
        return p1 > p2;
    } else {
        // 普通运算符左结合
        char op = (Character) op1;
        return p1 > p2 || (p1 == p2 && isLeftAssociative(op));
    }
}

调整右括号处理逻辑,弹出函数到输出队列:

else if (tokens[i] == ')') {
    while (!ops.isEmpty() && !((ops.peek() instanceof Character) && (Character) ops.peek() == '(')) {
        values.add(ops.pop());
    }
    ops.pop(); // 弹出左括号
    // 若栈顶是函数,加入输出队列
    if (!ops.isEmpty() && ops.peek() instanceof String) {
        values.add(ops.pop());
    }
}

新增函数计算方法并修改后缀表达式求值逻辑:

// 新增:科学函数计算
public static double applyFunction(String funcName, double num) {
    return switch(funcName.toLowerCase()) {
        case "sin" -> Math.sin(Math.toRadians(num)); // 若输入是角度,转弧度;否则直接用num
        case "cos" -> Math.cos(Math.toRadians(num));
        case "tan" -> Math.tan(Math.toRadians(num));
        case "sqrt" -> Math.sqrt(num);
        default -> throw new IllegalArgumentException("未知函数:" + funcName);
    };
}

// 修改:后缀表达式求值
Stack<Double> numStack = new Stack<>();
while (!values.isEmpty()) {
    Object val = values.poll();
    if (val instanceof Character) {
        char v = (Character) val;
        double num2 = numStack.pop();
        double num1 = numStack.pop();
        double ans = applyOp(v, num1, num2);
        numStack.push(ans);
    } else if (val instanceof String) {
        // 处理函数调用:弹出一个参数计算
        double num = numStack.pop();
        double ans = applyFunction((String) val, num);
        numStack.push(ans);
    } else {
        numStack.push((Double) val);
    }
}

补充说明

  • 函数优先级设为3(高于乘除的2),确保3*sin(45)先计算sin(45)再做乘法
  • 函数采用右结合逻辑,嵌套函数(如sin(cos(30)))会先计算内层的cos(30),符合数学规则
  • 若用户输入的是角度而非弧度,需在applyFunction中通过Math.toRadians()转换,反之则移除该转换

内容的提问来源于stack exchange,提问作者Komuthu Fernando

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 19:44:52