Java实现数学公式计算

需求

最近国家税务政策变动,财务报表需要更新公式和格式了,这类更新经常会发生,为了以后维护方便,想到了解析字符串格式的数学公式。查阅了一下资料,可以用逆波兰表达式实现。

中缀表达式转后缀表达式

中缀表达式:平常见到的表达式,如3+(5*(6-1/2))
后缀表达式:即逆波兰表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则),既然没了运算符的优先规则,那么计算机解析起来自然容易的多。上面的中缀表达式对应的后缀表达式[3, 5, 6, 1, 2, /, -, *, +]
解析公式需要先将通俗的中缀表达式转化成后缀表达式,具体算法如下:

  • 首先设置运算符的优先级(这样设置也是为了简化程序):
    • null 栈顶若为空,假设优先级为0
    • 左括号 优先级设为1
    • +- 优先级设为2
    • */ 优先级设为3
  • 从左向右遍历中缀表达式
    • 遇到数字直接输出
    • 遇到符号
      • 遇到左括号,直接压栈
      • 遇到右括号,弹栈输出直到弹出左括号(左括号不输出)
      • 遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。
  • 最后将符合栈弹栈并输出

后缀表达式计算结果

  • 从左往右遍历
  • 遇到数字直接放入容器
  • 遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器
  • 遍历结束后,容器中的数字即为运算结果

实现代码

以上算法不难实现,下面是我修改后的工具类,可以计算浮点数的公式,精确到2位有效数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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<String> test = Arrays.asList("3 + ( 5 * ( 6 - 1 / 2 ) )".split(" "));
System.out.println(getRpn(test));
System.out.println(calculate(test));
}

/**
* 计算指定公式的数值
*/
public static String calculate(List<String> formulaList) {
List<String> rpn = getRpn(formulaList);
return calculateRpn(rpn);
}

/**
* 中序转后序
*/
private static List<String> getRpn(List<String> formulaList) {
Stack<String> operators=new Stack<>();
List<String> 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<String> formulaList) {
Stack<BigDecimal> 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;
}
}
}

判断浮点数的正则

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 正则表达式工具类
* @author Administrator
*/
public class RegExpUtil {

/**
* 匹配浮点数
*/
public static final String FLOAT_NUMBER = "^(-?\\d+)(\\.\\d+)?$";

}
文章目录
  1. 1. 需求
    1. 1.1. 中缀表达式转后缀表达式
    2. 1.2. 后缀表达式计算结果
    3. 1.3. 实现代码
    4. 1.4. 判断浮点数的正则