/**
* 计算工具类
*/
@Slf4j
public class CalcUtil {
/**
* 表达式
*/
private String expression;
/**
* 存储计算所需要的数据,key为变量,value为具体值
*/
private final Map<String, BigDecimal> varMap = new HashMap<>();
/**
* 拆解后的表达式
*/
private final List<Object> expressionList = new ArrayList<>();
/**
* 逆波兰式
*/
private final List<Object> reversePolishNotation = new ArrayList<>();
/**
* 错误信息
*/
private final Set<String> errors = new HashSet<>();
/**
* 精度配置
*/
private Integer scale;
/**
* 四舍五入策略
*/
private RoundingMode roundingMode;
/**
* 最终的公式
*/
private StringBuilder finalExpression;
private CalcUtil() {
}
public static CalcUtil newCalc(String expression) {
CalcUtil calc = new CalcUtil();
return calc.setExpression(expression, null, null);
}
public static CalcUtil newCalc(String expression, Integer scale, RoundingMode roundingMode) {
CalcUtil calc = new CalcUtil();
return calc.setExpression(expression, scale, roundingMode);
}
/**
* 设置计算公式
*
* @param expression
* @param scale
* @param roundingMode
* @return
*/
private CalcUtil setExpression(String expression, Integer scale, RoundingMode roundingMode) {
if (expression == null) {
expression = "";
}
this.expression = expression;
this.scale = scale;
this.roundingMode = roundingMode;
this.parseExpression();
this.toReversePolishNotation();
return this;
}
/**
* 设置变量
*
* @param var
* @param number
*/
public CalcUtil setVar(String var, Number number) {
if (var == null || "".equals(var.trim()) || number == null) {
return this;
}
BigDecimal num;
if (number instanceof BigDecimal) {
num = (BigDecimal) number;
} else {
num = new BigDecimal(String.valueOf(number));
if (num.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) == 0) {
//如果小数位都是0,可以认为是整数,便于日志打印
num = num.setScale(0, RoundingMode.HALF_UP);
}
}
this.varMap.put(var.trim(), num);
return this;
}
/**
* 设置变量
*
* @param obj
* @return
*/
public CalcUtil setVar(Object obj) {
if (obj == null) {
return this;
}
//获取所有字段
List<Field> fieldList = new ArrayList<>();
Class<?> currentClass = obj.getClass();
while (currentClass != null) {
fieldList.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
//数字字段提取到varMap
for (Field field : fieldList) {
field.setAccessible(true);
try {
Object o = field.get(obj);
if (o instanceof Number) {
this.varMap.put(field.getName(), new BigDecimal(String.valueOf(o)));
}
} catch (IllegalAccessException e) {
this.errors.add(e.getMessage());
}
}
return this;
}
/**
* 计算结果
*
* @return
*/
public BigDecimal calc() {
String err = this.calcCheck();
if (!"".equals(err)) {
throw new RuntimeException(err);
}
Stack<BigDecimal> stack = new Stack<>();
for (Object item : this.reversePolishNotation) {
if (item instanceof SymbolEnum) {
SymbolEnum symbol = (SymbolEnum) item;
//计算符号
if (SymbolEnum.PERCENTAGE.equals(symbol)) {
//% 的计算
BigDecimal num = stack.pop();
//除100,精度加2,四舍五入实际用不到不影响
stack.push(num.divide(new BigDecimal("100"), num.scale() + 2, RoundingMode.HALF_UP));
continue;
}
//加减乘除计算
BigDecimal num2 = stack.pop();
BigDecimal num1 = stack.pop();
if (SymbolEnum.ADD.equals(symbol)) {
stack.push(num1.add(num2));
} else if (SymbolEnum.SUB.equals(symbol)) {
stack.push(num1.subtract(num2));
} else if (SymbolEnum.MULT.equals(symbol)) {
stack.push(num1.multiply(num2));
} else if (SymbolEnum.DIVI.equals(symbol)) {
stack.push(num1.divide(num2, scale, roundingMode));
} else if (SymbolEnum.POW.equals(symbol)) {
stack.push(num1.pow(num2.intValue()));
}
} else {
//计算对象
stack.push(this.varMap.get(String.valueOf(item)));
}
}
BigDecimal res = stack.pop();
String scaleStr = "";
if (this.scale != null && this.roundingMode != null) {
res = res.setScale(this.scale, this.roundingMode);
scaleStr = String.format("(scale=%s,rounding=%s)", this.scale, this.roundingMode);
}
String logStr = String.format("公式[ %s ] 计算[ %s=%s ] %s", this.expression, this.finalExpression, res.toPlainString(), scaleStr);
log.info(logStr);
return res;
}
/**
* 计算结果,异常则返回默认值
*
* @return
*/
public BigDecimal calcIfErr(BigDecimal defaultVal) {
try {
return this.calc();
} catch (Exception e) {
return defaultVal;
}
}
/**
* 拆解表达式
*/
private void parseExpression() {
this.expressionList.clear();
StringBuilder numStr = new StringBuilder();
//遍历字符串,提取 计算对象 和 计算符号
for (Character c : this.expression.toCharArray()) {
if (c == ' ') {
//忽略空字符串
continue;
}
SymbolEnum symbol = SymbolEnum.get(String.valueOf(c));
if (symbol == null) {
//非计算符号,是计算对象的一部分
numStr.append(c);
} else {
//计算符号
if (numStr.length() > 0) {
//添加计算对象
this.expressionList.add(numStr.toString());
numStr = new StringBuilder();
}
//添加计算符号
this.expressionList.add(symbol);
}
}
if (numStr.length() > 0) {
//添加最后一个计算对象
this.expressionList.add(numStr.toString());
}
}
/**
* 转逆波兰式
*/
private void toReversePolishNotation() {
this.reversePolishNotation.clear();
Stack<Object> stack = new Stack<>();
for (Object item : this.expressionList) {
if (item instanceof SymbolEnum) {
SymbolEnum symbol = (SymbolEnum) item;
//计算符号
if (SymbolEnum.LEFT.equals(symbol)) {
//左括号入栈
stack.push(symbol);
} else if (SymbolEnum.RIGHT.equals(symbol)) {
//右括号,出栈直到碰到左括号
while (!stack.isEmpty()) {
Object pop = stack.pop();
if (SymbolEnum.LEFT.equals(pop)) {
break;
}
this.reversePolishNotation.add(pop);
}
} else {
//其他符号,当前符号的优先等级小于栈内的,将栈内的全部出栈
while (!stack.isEmpty() && !SymbolEnum.LEFT.equals(stack.peek())
&& symbol.getPriority() <= ((SymbolEnum) stack.peek()).getPriority()) {
this.reversePolishNotation.add(stack.pop());
}
//将当前符号入栈
stack.push(symbol);
}
} else {
//计算对象
this.reversePolishNotation.add(item);
}
}
//栈中剩余对象
while (!stack.isEmpty()) {
this.reversePolishNotation.add(stack.pop());
}
}
/**
* 计算前校验,校验失败返回失败原因,成功返回空字符串
*
* @return
*/
private String calcCheck() {
this.finalExpression = new StringBuilder();
for (int i = 0; i < this.expressionList.size(); i++) {
Object item = this.expressionList.get(i);
int next = i + 1;
String nextStr = next < this.expressionList.size() ? String.valueOf(this.expressionList.get(next)) : "";
if (item instanceof SymbolEnum) {
SymbolEnum symbol = (SymbolEnum) item;
//符号
this.finalExpression.append(symbol.getSymbol());
if (SymbolEnum.DIVI.equals(symbol) && (this.scale == null || this.roundingMode == null)) {
this.errors.add("除法必须指定scale和roundingMode");
} else if (SymbolEnum.POW.equals(symbol) && NumberUtil.isNumber(nextStr) && !NumberUtil.isInteger(nextStr)) {
this.errors.add("幂运算必须是整数");
}
} else if (NumberUtil.isNumber(String.valueOf(item))) {
//数字
String numStr = String.valueOf(item);
this.varMap.put(numStr, new BigDecimal(numStr));
this.finalExpression.append(numStr);
} else {
//变量
String varStr = String.valueOf(item);
BigDecimal var = this.varMap.get(varStr);
if (var != null) {
this.finalExpression.append(var.toPlainString());
} else {
this.errors.add("[" + varStr + "]未指定具体值");
}
}
}
//逆波兰式处理过不应该存在括号
if (this.reversePolishNotation.contains(SymbolEnum.LEFT) || this.reversePolishNotation.contains(SymbolEnum.RIGHT)) {
this.errors.add("括号不匹配");
}
return String.join(",", this.errors);
}
enum SymbolEnum {
ADD("+", 0),
SUB("-", 0),
MULT("*", 1),
DIVI("/", 1),
PERCENTAGE("%", 2),
POW("^", 2),
LEFT("(", 3),
RIGHT(")", 3),
;
private final String symbol;
private final Integer priority;
SymbolEnum(String symbol, Integer priority) {
this.symbol = symbol;
this.priority = priority;
}
public String getSymbol() {
return symbol;
}
public Integer getPriority() {
return priority;
}
private static final Map<String, Object> CODE_MAP = new HashMap<>();
static {
for (SymbolEnum e : SymbolEnum.values()) {
CODE_MAP.put(e.getSymbol(), e);
}
}
/**
* 通过symbol获取对应枚举
*
* @param symbol
* @return
*/
public static SymbolEnum get(String symbol) {
return (SymbolEnum) CODE_MAP.get(symbol);
}
}
}