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

Kotlin Android算术表达式解析器Bug排查与替代方案咨询

问题排查与修复方案

我来帮你搞定这个表达式解析的Bug,先说说问题出在哪儿,再给你修复后的代码,同时也推荐几个更省心的替代方案。

核心Bug原因分析

  1. 操作符匹配完全错误
    你的代码里所有操作符都写成了带双引号的形式(比如"\"-\""),但实际输入的表达式里的减号、加号都是不带引号的!这导致代码根本找不到正确的操作符,后续截取数字时自然会拿到空字符串,转Double就触发异常了。

  2. 减法操作数顺序搞反了
    在basic函数里,减法逻辑是rightNum?.toDouble()!! - leftNum?.toDouble()!!,这完全颠倒了左右操作数的顺序——比如a - b应该是a减b,你写成了b减a,不仅结果错误,还会在处理连续减法时加剧字符串截取的问题。

  3. 正负号与减法操作符混淆
    代码没有区分作为负号的-(比如-8里的-)和作为减法操作符的-(比如10-5里的-)。当表达式出现负数时,开头的-会被当成操作符处理,截取左数字时就会得到空字符串,触发异常。

  4. 数字截取逻辑有缺陷
    原来的parseSimple通过操作符索引的前后位置来截取数字,这种方式在处理负数、连续操作符时非常容易拿到空字符串,根本不可靠。

修复后的完整代码

下面是修复后的代码,解决了上述所有问题:

import kotlin.math.pow

fun basic(leftNum: String?, rightNum: String?, op: String?): Double? {
    return when (op) {
        "+" -> leftNum?.toDouble()!! + rightNum?.toDouble()!!
        "-" -> leftNum?.toDouble()!! - rightNum?.toDouble()!!
        "*" -> leftNum?.toDouble()!! * rightNum?.toDouble()!!
        "^" -> leftNum?.toDouble()!!.pow(rightNum?.toDouble()!!)
        "/" -> {
            val right = rightNum?.toDouble()!!
            if (right == 0.0) throw ArithmeticException("Division by zero")
            leftNum?.toDouble()!! / right
        }
        else -> null
    }
}

fun parseSimple(query: String?): Double? {
    var calcQuery = query?.trim() ?: return null

    // 预处理正负号:把负号转换为合法的减法操作,避免被当成操作符误判
    calcQuery = calcQuery.replace("^-", "0-") // 开头的负号 → 0-xxx
        .replace("\\(-", "(0-") // 括号后的负号 → (0-xxx)
        .replace("\\+-", "-") // 正号加负号 → 负号
        .replace("--", "+") // 负号加负号 → 正号

    val operations = listOf("^", "/", "*", "-", "+")
    var hasOperations = operations.any { calcQuery.contains(it) }

    while (hasOperations) {
        var processed = false
        // 按优先级处理:先乘方,再乘除,最后加减
        for (op in operations) {
            val opIndex = calcQuery.indexOf(op)
            if (opIndex == -1) continue

            // 跳过作为负号的"-"(已经预处理过,这里只处理真正的减法操作符)
            if (op == "-" && (opIndex == 0 || calcQuery[opIndex - 1] in listOf('(', '+', '-', '*', '/', '^'))) {
                continue
            }

            // 向左找左边数字的起始位置
            var leftIndex = opIndex - 1
            while (leftIndex >= 0 && (calcQuery[leftIndex].isDigit() || calcQuery[leftIndex] == '.')) {
                leftIndex--
            }
            leftIndex++

            // 向右找右边数字的结束位置
            var rightIndex = opIndex + 1
            while (rightIndex < calcQuery.length && (calcQuery[rightIndex].isDigit() || calcQuery[rightIndex] == '.')) {
                rightIndex++
            }
            rightIndex--

            // 校验数字是否有效
            if (leftIndex > opIndex - 1 || rightIndex < opIndex + 1) {
                throw IllegalArgumentException("Invalid expression: $calcQuery")
            }

            val leftNum = calcQuery.substring(leftIndex, opIndex)
            val rightNum = calcQuery.substring(opIndex + 1, rightIndex + 1)
            val result = basic(leftNum, rightNum, op) ?: throw IllegalArgumentException("Invalid operation: $op")

            // 替换当前计算部分为结果
            calcQuery = calcQuery.substring(0, leftIndex) + result.toString() + calcQuery.substring(rightIndex + 1)
            processed = true
            break // 处理完一个操作符后重新扫描整个表达式
        }
        hasOperations = if (processed) operations.any { calcQuery.contains(it) } else false
    }

    return try {
        calcQuery.toDouble()
    } catch (e: NumberFormatException) {
        throw IllegalArgumentException("Invalid number format: $calcQuery")
    }
}

fun getBrackets(query: String?): List<Int> {
    val q = query ?: return listOf(-1, -1)
    var depth = 0
    // 先找最内层的括号(没有嵌套的括号对)
    for (i in q.indices) {
        if (q[i] == '(') depth++
        else if (q[i] == ')') {
            depth--
            if (depth == 0) {
                // 找到匹配的右括号,反向找对应的左括号
                for (j in i-1 downTo 0) {
                    if (q[j] == '(') {
                        val inner = q.substring(j+1, i)
                        if (!inner.contains('(') && !inner.contains(')')) {
                            return listOf(j, i)
                        }
                        break
                    }
                }
            }
        }
    }
    // 如果没有最内层,找第一个完整的括号对
    val firstOpen = q.indexOf('(')
    if (firstOpen == -1) return listOf(-1, -1)
    var depth2 = 1
    for (i in firstOpen+1 until q.length) {
        if (q[i] == '(') depth2++
        else if (q[i] == ')') depth2--
        if (depth2 == 0) return listOf(firstOpen, i)
    }
    return listOf(-1, -1) // 括号不匹配
}

fun evaluate(query: String?): Double? {
    var calcQuery = query?.trim() ?: return null

    // 校验括号是否匹配
    var depth = 0
    for (c in calcQuery) {
        if (c == '(') depth++
        else if (c == ')') {
            depth--
            if (depth < 0) throw IllegalArgumentException("Mismatched parentheses: extra closing bracket")
        }
    }
    if (depth != 0) throw IllegalArgumentException("Mismatched parentheses: missing closing bracket")

    var index = 0
    val maxIterations = 100 // 防止极端情况无限循环
    while (calcQuery.contains('(') && index < maxIterations) {
        val bracketPair = getBrackets(calcQuery)
        val start = bracketPair[0]
        val end = bracketPair[1]
        if (start == -1 || end == -1) break

        val innerExpr = calcQuery.substring(start + 1, end)
        val innerResult = evaluate(innerExpr) ?: throw IllegalArgumentException("Invalid expression inside brackets: $innerExpr")

        calcQuery = calcQuery.substring(0, start) + innerResult.toString() + calcQuery.substring(end + 1)
        index++
    }

    return parseSimple(calcQuery)
}

fun main() {
    print("Enter the equation: ")
    val equation = readLine()
    try {
        val result = evaluate(equation)
        println("Result: $result")
    } catch (e: Exception) {
        println("Error: ${e.message}")
    }
}

更可靠的替代方案

如果你不想自己维护表达式解析逻辑,推荐使用成熟的库或者内置工具:

方案1:使用Exp4j(Android友好)

这是一个轻量级的Java表达式解析库,Android可以直接使用,支持所有基本算术操作和括号。

  1. 在Android项目的build.gradle(Module级别)添加依赖:
implementation 'net.objecthunter:exp4j:0.4.8'
  1. 使用示例:
import net.objecthunter.exp4j.Expression
import net.objecthunter.exp4j.ExpressionBuilder

fun evaluateWithExp4j(query: String?): Double? {
    return try {
        val expression = ExpressionBuilder(query).build()
        expression.evaluate()
    } catch (e: Exception) {
        println("Error evaluating expression: ${e.message}")
        null
    }
}

// 在Activity或main函数中调用
fun main() {
    print("Enter the equation: ")
    val equation = readLine()
    println("Result: ${evaluateWithExp4j(equation)}")
}

方案2:使用Kotlin脚本引擎(仅限非Android或权限允许的场景)

Kotlin内置了脚本引擎,可以直接解析表达式,但在Android中可能有性能或权限限制:

import javax.script.ScriptEngineManager

fun evaluateWithScriptEngine(query: String?): Double? {
    val engine = ScriptEngineManager().getEngineByName("kotlin")
    return try {
        engine.eval(query) as Double
    } catch (e: Exception) {
        println("Error evaluating expression: ${e.message}")
        null
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 15:37:33