背景介绍
在开发中,我们经常需要对用户输入的数学表达式进行实时计算。例如,在前端页面上展示动态计算结果。然而,由于前端通常采用JavaScript进行计算,而JavaScript的浮点运算存在精度问题,导致部分结果与后端基于BigDecimal
的计算不一致。
为了彻底解决这一问题,我们可以在后台编写一个高精度的数学表达式计算工具类,并将其 API 暴露给前端调用,从而杜绝前后端计算结果不一致的问题。
实现思路
-
递归与替换结合:
- 创建一个基础方法
A
,该方法仅支持两数之间的简单加减乘除运算。 - 利用递归方法处理复杂的表达式,逐步拆解成简单运算。
- 创建一个基础方法
-
处理优先级:
- 对于加减、乘除运算,遵循数学优先级规则,先处理乘除,后处理加减。
- 对于带有括号的表达式,优先计算最内层括号的内容。
-
逐步简化表达式:
- 每次计算后,使用结果替换表达式中的对应部分,直到表达式被完全解析。
以下是一个具体的例子来演示这一过程:
表达式:2+(6*2+18-2*4)/2
- 第 1 步:计算括号内部的乘法运算,
2+(12+18-2*4)/2
- 第 2 步:继续计算,
2+(12+18-8)/2
- 第 3 步:进一步简化,
2+(30-8)/2
- 第 4 步:计算括号内部的减法,
2+22/2
- 第 5 步:处理除法,
2+11
- 第 6 步:最终结果,
13
实际代码实现
java">import java.math.BigDecimal;public class BigDecimalExpressionEvaluator {public static void main(String[] args) {String expression = "33.241*3480/1.13";long startString = System.nanoTime();BigDecimal stringResult = calculate(expression);long endString = System.nanoTime();System.out.println("String Parsing Calculation Result: " + stringResult.stripTrailingZeros().toPlainString());System.out.println("String Parsing Calculation Time: " + (endString - startString) + " ns");}/*** 主计算入口,传入一个数学表达式返回计算结果*/public static BigDecimal calculate(String expression) {// 1. 预处理表达式,标准化字符expression = standardizeExpression(expression);// 2. 去除所有空格expression = expression.replaceAll(" ", "");// 3. 递归解析并计算结果return evaluate(expression);}/*** 标准化表达式,将非标准符号替换为标准符号。*/private static String standardizeExpression(String expression) {// 替换 Unicode 减号(U+2212)为 ASCII 减号(U+002D)expression = expression.replace('\u2212', '-');// 如果有其他类似非标准字符,也可以在这里扩展,比如替换全角数字/符号// 替换全角空格为半角空格expression = expression.replace('\u3000', ' ');// 替换全角加号(U+FF0B)为 ASCII 加号(U+002B)expression = expression.replace('\uFF0B', '+');// 替换全角乘号(U+00D7)为 ASCII 星号(U+002A)expression = expression.replace('\u00D7', '*');// 替换全角除号(U+00F7)为 ASCII 斜杠(U+002F)expression = expression.replace('\u00F7', '/');return expression;}/*** 递归解析和计算表达式*/private static BigDecimal evaluate(String expression) {// 处理括号,从内到外递归解析while (expression.contains("(")) {int openIndex = expression.lastIndexOf("("); // 找到最内层左括号int closeIndex = expression.indexOf(")", openIndex); // 找到与之匹配的右括号String innerExpression = expression.substring(openIndex + 1, closeIndex); // 括号内的表达式BigDecimal innerResult = evaluate(innerExpression); // 递归计算括号内的值expression = expression.substring(0, openIndex) + innerResult + expression.substring(closeIndex + 1); // 替换括号内容为结果}// 递归处理乘除(优先级高)while (expression.contains("*") || expression.contains("/")) {expression = handleOperators(expression, "*/");}// 递归处理加减(优先级低)while (expression.contains("+") || expression.contains("-")) {expression = handleOperators(expression, "+-");}return new BigDecimal(expression);}/*** 处理指定运算符优先级的简单表达式*/private static String handleOperators(String expression, String operators) {for (int i = 0; i < expression.length(); i++) {char ch = expression.charAt(i);if (operators.indexOf(ch) != -1) { // 当前字符是目标操作符// 找到左操作数int leftStart = i - 1;while (leftStart >= 0 && (Character.isDigit(expression.charAt(leftStart)) || expression.charAt(leftStart) == '.')) {leftStart--;}BigDecimal left = new BigDecimal(expression.substring(leftStart + 1, i));// 找到右操作数int rightEnd = i + 1;while (rightEnd < expression.length() && (Character.isDigit(expression.charAt(rightEnd)) || expression.charAt(rightEnd) == '.')) {rightEnd++;}BigDecimal right = new BigDecimal(expression.substring(i + 1, rightEnd));// 计算当前操作符的结果BigDecimal result = applyOperator(ch, left, right);// 替换表达式中对应的部分expression = expression.substring(0, leftStart + 1) + result + expression.substring(rightEnd);break; // 每次只处理一个操作符,递归继续}}return expression;}/*** 对两个数应用操作符*/private static BigDecimal applyOperator(char op, BigDecimal left, BigDecimal right) {switch (op) {case '+':return left.add(right);case '-':return left.subtract(right);case '*':return left.multiply(right);case '/':if (right.compareTo(BigDecimal.ZERO) == 0) {throw new ArithmeticException("Cannot divide by zero");}return left.divide(right, 8, BigDecimal.ROUND_HALF_UP); // 保留 8 位小数default:throw new UnsupportedOperationException("Unsupported operator: " + op);}}
}
由于对复杂算法没有要求,所以只是简单的实现了加减乘除的基本运算,需要的朋友可以自行扩展。改代码没有在生产环境实际使用,请自行测试