--- title: Java实现数学公式计算 date: 2019-05-20 17:48:52 description: Java实现数学公式计算,支持浮点数,使用逆波兰表达式计算 categories: [技术总结] tags: [Java] --- ## 需求 最近国家税务政策变动,财务报表需要更新公式和格式了,这类更新经常会发生,为了以后维护方便,想到了解析字符串格式的数学公式。查阅了一下资料,可以用逆波兰表达式实现。 ### 中缀表达式转后缀表达式 中缀表达式:平常见到的表达式,如`3+(5*(6-1/2))` 后缀表达式:即逆波兰表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则),既然没了运算符的优先规则,那么计算机解析起来自然容易的多。上面的中缀表达式对应的后缀表达式`[3, 5, 6, 1, 2, /, -, *, +]` 解析公式需要先将通俗的中缀表达式转化成后缀表达式,具体算法如下: - 首先设置运算符的优先级(这样设置也是为了简化程序): - `null` 栈顶若为空,假设优先级为0 - `左括号` 优先级设为1 - `+-` 优先级设为2 - `*/` 优先级设为3 - 从左向右遍历中缀表达式 - 遇到数字直接输出 - 遇到符号 - 遇到左括号,直接压栈 - 遇到右括号,弹栈输出直到弹出左括号(左括号不输出) - 遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。 - 最后将符合栈弹栈并输出 ### 后缀表达式计算结果 - 从左往右遍历 - 遇到数字直接放入容器 - 遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器 - 遍历结束后,容器中的数字即为运算结果 ### 实现代码 以上算法不难实现,下面是我修改后的工具类,可以计算浮点数的公式,精确到2位有效数字 ```java import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; import static com.hongfund.efi.utils.RegExpUtil.FLOAT_NUMBER; import static java.math.BigDecimal.ZERO; import static java.math.RoundingMode.HALF_UP; /** * 数学公式计算 * @author chenbin */ public class RpnUtils { public static void main(String[] args) { List test = Arrays.asList("3 + ( 5 * ( 6 - 1 / 2 ) )".split(" ")); System.out.println(getRpn(test)); System.out.println(calculate(test)); } /** * 计算指定公式的数值 */ public static String calculate(List formulaList) { List rpn = getRpn(formulaList); return calculateRpn(rpn); } /** * 中序转后序 */ private static List getRpn(List formulaList) { Stack operators=new Stack<>(); List result = new ArrayList<>(); for (String formula : formulaList) { if (formula.matches(FLOAT_NUMBER)) { result.add(formula); } else if (formula.equals("(")) { operators.push(formula); } else if (formula.equals(")")) { while (!operators.peek().equals("(")) { result.add(operators.pop()); } operators.pop(); } else { while (operators.size() != 0 && getLevel(operators.peek()) >= getLevel(formula)) { result.add(operators.pop()); } operators.push(formula); } } while (operators.size() != 0) { result.add(operators.pop()); } return result; } /** * 逆序计算结果 */ private static String calculateRpn(List formulaList) { Stack s=new Stack<>(); for (String formula : formulaList) { if (formula.matches(FLOAT_NUMBER)) { s.push(BigDecimalUtils.getDecimalValue(formula)); } else { BigDecimal b = s.pop(); BigDecimal a = s.pop(); BigDecimal temp = ZERO; switch (formula) { case "+" : temp = a.add(b);break; case "-" : temp = a.subtract(b);break; case "*" : temp = a.multiply(b);break; case "/" : temp = a.divide(b, 2, HALF_UP);break; case "max" : temp = a.max(b);break; } s.push(temp); } } return FormatUtils.DECIMAL_FORMAT_2.format(s.pop()); } /** * 运算符优先级 */ private static int getLevel(String operator) { if (StringUtils.isBlank(operator)) { return 0; } switch (operator) { case "(" : return 0; case "+" : case "-" : return 1; case "*" : case "/" : case "max" : return 2; default : return -1; } } } ``` ### 判断浮点数的正则 ```java /** * 正则表达式工具类 * @author Administrator */ public class RegExpUtil { /** * 匹配浮点数 */ public static final String FLOAT_NUMBER = "^(-?\\d+)(\\.\\d+)?$"; } ```