新代码对计算方式做了优化,最近有新需求,需要加上根号处理,故对代码进行了调整
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
@Slf4j
public class CalculatorFormulaNewUtil {
private static final String NUMBER_REGEX = "[0-9]";
/**
* 中缀变后缀
*/
public static List<String> changeInfixExpressionToPostfixExpression(String input){
List<String> resultList=new ArrayList<>();
Stack<String> tempStack= new Stack<>();
// T替换回sqrt
String[] splitArray = input.split(" ");
//去除字符串数组中的空串
splitArray = delNilString(splitArray);
for (int i=0;i<splitArray.length;i++){
if (")".equals(splitArray[i])){
while (!"(".equals(tempStack.peek())){
resultList.add(tempStack.pop());
}
// 如果去除左括号之后 前一个是开方的话 直接加入
if ("T".equals(tempStack.peek())){
resultList.add(tempStack.pop());
}
tempStack.pop();//去除前面的左括号
}else if ("(".equals(splitArray[i]) || "T".equals(splitArray[i])){
//如果是左括号,那么直接添加进去
// 如果是开方sqrt 说明需要直接对前一个计算结果进行开方 先进行压栈 因为需要最后的结果才进行开方
tempStack.add(splitArray[i]);
}else if ("+".equals(splitArray[i])|| "-".equals(splitArray[i])){
if (tempStack.empty()|| "(".equals(tempStack.peek())){
tempStack.add(splitArray[i]);
}else if ("+".equals(tempStack.peek())|| "-".equals(tempStack.peek()) || "T".equals(tempStack.peek())){
//读临时栈里的顶部数据,如果也是加减就取出来一个到结果列,这个放临时栈,如果是乘除就开始取到右括号为止
resultList.add(tempStack.pop());
tempStack.add(splitArray[i]);
}else {
while (!tempStack.empty()){
if ("(".equals(tempStack.peek())){
break;
}else {
resultList.add(tempStack.pop());
}
}
tempStack.add(splitArray[i]);
}
}else if ("*".equals(splitArray[i])|| "/".equals(splitArray[i])){
//如果是乘除
if (!tempStack.empty()){
//判断临时栈里栈顶是啥,如果是乘除,取一个出来到结果列,添这个进临时栈
if ("*".equals(tempStack.peek())|| "/".equals(tempStack.peek()) || "T".equals(tempStack.peek())){
resultList.add(tempStack.pop());
}
}
tempStack.add(splitArray[i]);
} else {
//说明是非符号,都添加进去
resultList.add(splitArray[i]);
}
}
//遍历完了,把临时stack里的东西都加到结果stack里去
while (!tempStack.empty()){
resultList.add(tempStack.pop());
}
return resultList;
}
/**
* 去除空串
*/
public static String[] delNilString(String[] arr){
//用StringBuffer来存放数组中的非空元素,用“;”分隔
StringBuilder sb = new StringBuilder();
for(int i=0; i<arr.length; i++) {
if("".equals(arr[i])) {
continue;
}
sb.append(arr[i]);
if(i != arr.length - 1) {
sb.append(";");
}
}
//用String的split方法分割,得到数组
arr = sb.toString().split(";");
return arr;
}
/**
* 获取结果
*/
public static BigDecimal getResult(String input){
if (StrUtil.isEmpty(input)){
log.debug("formula is null, formula = {}", input);
return null;
}
//规范输入形式,避免用户输入中文括号
input = input.toLowerCase().replaceAll("(","(");
input = input.replaceAll(")",")");
// sqrt用T代替 避免后面不好split
input = input.replaceAll("sqrt", "T");
//对输入公式,按符号/数字,用空格分开,以便后面分组
String[] inputs = input.split("");
StringBuilder format= new StringBuilder();
for (String s : inputs) {
if ("(".equals(s) || ")".equals(s) || "+".equals(s) || "-".equals(s) || "*".equals(s) || "/".equals(s)
|| "T".equals(s)) {
format.append(" ").append(s).append(" ");
} else {
format.append(s);
}
}
List<String> strings = changeInfixExpressionToPostfixExpression(format.toString());
Stack<String> stack= new Stack<>();
try {
for (String string : strings) {
if ("+".equals(string)) {
BigDecimal a = new BigDecimal(stack.pop());
BigDecimal b = new BigDecimal(stack.pop());
stack.add(b.add(a).toString());
} else if ("-".equals(string)) {
BigDecimal a = new BigDecimal(stack.pop());
BigDecimal b = new BigDecimal(stack.pop());
stack.add(b.subtract(a).toString());
} else if ("*".equals(string)) {
BigDecimal a = new BigDecimal(stack.pop());
BigDecimal b = new BigDecimal(stack.pop());
stack.add(b.multiply(a).toString());
} else if ("/".equals(string)) {
BigDecimal a = new BigDecimal(stack.pop());
BigDecimal b = new BigDecimal(stack.pop());
//这里的1000是做除法以后计算的精确位数,就算1000位也并不会拖慢程序速度,一个公式0.01秒内就能算完,后面的是除不尽的四舍五入
stack.add(b.divide(a, 1000, BigDecimal.ROUND_HALF_DOWN).toString());
} else if (string.contains(".")){
// 需要判断是否是个数字
// 去除括号校验一下
String checkString = string.replaceAll("[()]","");
boolean number = NumberUtil.isNumber(checkString);
if (!number){
log.debug("formula error... formula = {}", input);
return null;
}
// 要求.之前和.之后必须是数字0-9 防止出现.n n.这种情况
String firstString = checkString.substring(0, 1);
String lastString = checkString.substring(checkString.length() -1);
if(!Pattern.matches(NUMBER_REGEX, firstString)
|| !Pattern.matches(NUMBER_REGEX, lastString)){
log.debug("formula error... formula = {}", input);
return null;
}
stack.add(string);
} else if ("T".equals(string)){
// 去除栈中第一个数开方再塞回去
BigDecimal a = new BigDecimal(stack.pop());
stack.add(BigDecimal.valueOf(Math.sqrt(a.doubleValue()))
.setScale(1000, RoundingMode.HALF_UP).toPlainString());
} else {
// 到这只可能是数字
stack.add(string);
}
}
// 上面的算完 按理说stack里面只能有一个值
// 如果出现两个 这说明存在两个数字相连但是没有运算符的情况
if (stack.size() > 1){
log.debug("formula error... formula = {}", input);
return null;
}
//返回的时候格式化一下,取四舍五入小数点后两位
return new BigDecimal(stack.pop()).setScale(4, RoundingMode.HALF_UP);
} catch (Exception e){
// 公式有误
log.debug("formula error ... formula = {}", input);
return null;
}
}
// public static void main(String[] args) {
// System.out.println(getResult("2 + 3 + Sqrt1+ (1+ 2 + 2 * sQrt(2+14) * 3) + SQRT(5*5)"));
// }
}