公式计算器实现

目标:

实现一个简易的公式计算器,如根据用户输入 2+4*3-2-(2*8) 公式可以自行求解

实现思路:

​ 概念:

​ 中缀式:如2+4*3-2-(2*8)

​ 后缀式:如上中缀式转换为后缀式为

1. 公式转换,解决公式开头为负数的问题。
1. 将中缀式转换为后缀式并放入后缀栈中
1. 将后缀栈中的数据弹出计算并放入结果栈中

逆波兰(中缀转后缀式)总结:

  1. 从左往右依次处理

  2. 遇见数字直接追加写入公式中,如果遇见的是运算符。

    2.1 遇见的运算符与栈顶中的运算符进行优先级对比,如栈中不存在运算符则直接放入栈中,优先级小于等于栈顶运算符时循环取出栈顶运算符追加入公式中,最后将当前运算符放入栈中。优先级大于栈顶运算符时直接放入栈中。

    2.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
package com.spidermen.math;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class BaseMethod {
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 2;

// 这个类不能实例化
private ArithHelper() {
}

/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

public static double add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).doubleValue();
}

/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

public static double sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).doubleValue();
}

/**
* 提供精确的乘法运算。
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

public static double mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).doubleValue();
}

/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return div(b1, b2, DEF_DIV_SCALE);
}

public static double div(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return div(b1,b2,DEF_DIV_SCALE);
}

/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
return v1.divide(v2, scale, RoundingMode.HALF_UP).doubleValue();
}

}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package com.spidermen.math;


import com.alibaba.fastjson.JSONArray;

import java.util.Collections;
import java.util.Stack;

/**
* 数学表达式计算核心功能
*/
public class Calculator {
private Stack<String> postfixStack = new Stack<String>();
// 后缀式栈
private Stack<Character> opStack = new Stack<Character>();
// 运算符栈
private int[] operatPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};

// 运用运算符ASCII码-40做索引的运算符优先级
public static double conversion(String expression) {
// log.info("公式[{}]开始计算!",expression);
double result = 0;
Calculator cal = new Calculator();
try {
expression = transform(expression);
result = cal.calculate(expression);
} catch (Exception e) {
e.printStackTrace();
// 运算错误返回0
return 0;
}
// return new String().valueOf(result);
// log.info("公式[{}]计算结果为:{}",expression,result);
return result;
}

/**
* 将表达式中负数的符号更改
*
* @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1)
* @return
*/
private static String transform(String expression) {
char[] arr = expression.toCharArray();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == '-') {
if (i == 0) {
arr[i] = '~';
} else {
char c = arr[i - 1];
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') {
arr[i] = '~';
}
}
}
}
if (arr[0] == '~') {
arr[0] = '-';
return "0" + new String(arr);
}
int i=0;
while (arr[i] =='('){
i++;
}
if(arr[i]=='-'){
return new String(arr,0,i)+"0"+new String(arr,i,arr.length-i);
}else {
return new String(arr);
}

}

/**
* 按照给定的表达式计算
*
* @param expression 要计算的表达式例如:5+12*(3+5)/7
* @return
*/
public double calculate(String expression) {
Stack<String> resultStack = new Stack<String>();
prepare(expression);
Collections.reverse(postfixStack);
// 将后缀式栈反转
String firstValue, secondValue, currentValue;
// 参与计算的第一个值,第二个值和算术运算符
while (!postfixStack.isEmpty()) {
currentValue = postfixStack.pop();
if (!isOperator(currentValue.charAt(0))) {
// 如果不是运算符则存入操作数栈中
currentValue = currentValue.replace("~", "-");
resultStack.push(currentValue);
} else {
// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算
secondValue = resultStack.pop();
firstValue = resultStack.pop();
// 将负数标记符改为负号
firstValue = firstValue.replace("~", "-");
secondValue = secondValue.replace("~", "-");
String tempResult = calculate(firstValue, secondValue, currentValue.charAt(0));
resultStack.push(tempResult);
}
}
return Double.valueOf(resultStack.pop());
}

/**
* 数据准备阶段将表达式转换成为后缀式栈
*
* @param expression
*/
private void prepare(String expression) {
System.out.println("中缀式为: "+expression);
opStack.push(',');
// 运算符放入栈底元素逗号,此符号优先级最低
char[] arr = expression.toCharArray();
int currentIndex = 0;
// 当前字符的位置
int count = 0;
// 上次算术运算符到本次算术运算符的字符的长度便于获取之间的数值
char currentOp, peekOp;
// 当前操作符和栈顶操作符
for (int i = 0; i < arr.length; i++) {
currentOp = arr[i];
if (isOperator(currentOp)) {
// 如果当前字符是运算符
if (count > 0) {
postfixStack.push(new String(arr, currentIndex, count));
// 取两个运算符之间的数字
}
peekOp = opStack.peek();
if (currentOp == ')') {
// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号
while (opStack.peek() != '(') {
postfixStack.push(String.valueOf(opStack.pop()));
}
opStack.pop();
} else {
while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) {
postfixStack.push(String.valueOf(opStack.pop()));
peekOp = opStack.peek();
}
opStack.push(currentOp);
}
count = 0;
currentIndex = i + 1;
} else {
count++;
}
}
if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {
// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中
postfixStack.push(new String(arr, currentIndex, count));
}
while (opStack.peek() != ',') {
postfixStack.push(String.valueOf(opStack.pop()));
// 将操作符栈中的剩余的元素添加到后缀式栈中
}

System.out.println("后缀式为: "+ JSONArray.toJSONString(postfixStack));
}

/**
* 判断是否为算术符号
*
* @param c
* @return
*/
private Boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
}

/**
* 利用ASCII码-40做下标去算术符号优先级
*
* @param cur
* @param peek
* @return
*/
public Boolean compare(char cur, char peek) {
// 如果是peek优先级高于cur,返回true,默认都是peek优先级要低
Boolean result = false;
if (operatPriority[(peek) - 40] >= operatPriority[(cur) - 40]) {
result = true;
}
return result;
}

/**
* 按照给定的算术运算符做计算
*
* @param firstValue
* @param secondValue
* @param currentOp
* @return
*/
private String calculate(String firstValue, String secondValue, char currentOp) {
String result = "";
switch (currentOp) {
case '+':
result = String.valueOf(BaseMethod.add(firstValue, secondValue));
break;
case '-':
result = String.valueOf(BaseMethod.sub(firstValue, secondValue));
break;
case '*':
result = String.valueOf(BaseMethod.mul(firstValue, secondValue));
break;
case '/':
result = String.valueOf(BaseMethod.div(firstValue, secondValue));
break;
}
return result;
}
}