起因
有些时候要计算很复杂的场景,各种的加减乘除,甚至还有取模,取整,不止是要计算出结果,还的保留计算公式,所有就写了个公式计算引擎,真的不想用表达式引擎,觉得太重了!
特点
支持拓展
- 加减乘除
- 取模
- 取整
- 括号
- 科学计
演示
计算字符串:"3 * (2 + 3) / 2" 运算结果:7.5
System.out.println(executeExpression("3 * (2 + 3) / 2"));
代码实现
/**
* 公式解析引擎
* <p>
* Created by songzhaoying on 2020/8/11 14:44.
*
* @author songzhaoying@com.
* @date 2020/8/11 14:44.
*/
public class CalculatorNewUtil {
private static final Logger logger = LoggerFactory.getLogger(CalculatorNewUtil.class);
/**
* 表达式字符合法性校验正则模式
*/
private static final String EXPRESSION_PATTERN_REGEX = "[0-9\\.\\+\\-\\*\\/\\\(\\)_%Ee\\s]+";
/**
* 判断数字正则
*/
private static final String NUM_REGEX = "\d+(\\.)?\\d*((E|e|E\\+|e\\+|E-|e-)\\d+)?";
/**
* 操作符 需要加 空格
* ((|)|+|-|*|/|_|%)
*/
private static final String ADD_SPACE_REGEX = "((\\d+.?\\d*[Ee]([+\\-])?\\d+)|([()+\\-*/_%]))";
/**
* 空格
*/
private static final String SPACE = "\\s";
/**
* 计算中保留小数位数
*/
private static final int CAL_SCALE_SIZE = 10;
/**
* 计算中保留小数位数 处理原则
*/
private static final int CAL_SCALE_ROUND = BigDecimal.ROUND_HALF_DOWN;
/**
* 保留小数
*
* @param calExpressionStr
* @param scaleSize
* @param bigRoundType
* @return
* @throws Exception
*/
public static BigDecimal executeExpression(String calExpressionStr, int scaleSize, int bigRoundType) throws Exception {
return executeExpression(calExpressionStr).setScale(scaleSize, bigRoundType);
}
/**
* 计算值 10 位小数 0 去掉
*
* @param calExpressionStr
* @return
* @throws Exception
*/
public static BigDecimal executeExpression(String calExpressionStr) throws Exception {
return new BigDecimal(calculate(calExpressionStr).stripTrailingZeros().toPlainString());
}
/**
* 计算 中序 字符串
*
* @param calExpressionStr
* @return
*/
public static BigDecimal calculate(String calExpressionStr) throws Exception {
calExpressionStr = check2RepairExpression(calExpressionStr);
List<String> inorderExpressionList = getInorderExpressionList(calExpressionStr);
// 生成 逆波兰 表达式 list
List<String> suffixExpressionList = getSuffixExpressionList(inorderExpressionList);
if (logger.isDebugEnabled()) {
logger.info("中序表达式:{}", JsonUtil.writeValueAsString(inorderExpressionList));
logger.info("后缀表达式:{}", JsonUtil.writeValueAsString(suffixExpressionList));
}
return calculate(suffixExpressionList);
}
/**
* 生成中序表达式 list
*
* @param calExpressionStr
* @return
*/
private static List<String> getInorderExpressionList(String calExpressionStr) {
// 生成 中序表达式 list
return Arrays.stream(calExpressionStr.split(SPACE))
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
}
/**
* 计算 逆波兰 / 后序表达式 List
*
* @param suffixExpressionList
* @return
*/
private static BigDecimal calculate(List<String> suffixExpressionList) throws Exception {
if (CollectionUtils.isEmpty(suffixExpressionList)) {
return BigDecimal.ZERO;
}
Stack<BigDecimal> numStack = new Stack<>();
for (String op2num : suffixExpressionList) {
if (StringUtils.isBlank(op2num)) {
continue;
}
// 使用正则表达式取出
if (isNumber(op2num)) {
// 数字
numStack.push(new BigDecimal(op2num));
} else {
// 运算符
OptEnum opEnum = OptEnum.getEnum(op2num);
if (opEnum.getCalFunction() == null) {
throw new RuntimeException("操作符不支持!");
}
BigDecimal bigDecimal2 = numStack.pop();
BigDecimal bigDecimal1 = numStack.pop();
BigDecimal resultBigDecimal = opEnum.getCalFunction().apply(bigDecimal1, bigDecimal2);
// 结果入栈
numStack.push(resultBigDecimal);
}
}
return numStack.pop();
}
/**
* 生成逆波兰 / 后续表达式 list
*
* @param inorderExpressionList
* @return
*/
private static List<String> getSuffixExpressionList(List<String> inorderExpressionList) {
Stack<String> opStack = new Stack<>();
// 不通过中间栈,在进行逆序处理,直接输出到list中,就是需要的逆波兰表达式
List<String> resList = new ArrayList<>(inorderExpressionList.size());
for (String op2num : inorderExpressionList) {
if (isNumber(op2num)) {
// 数字
resList.add(op2num);
} else if (Objects.equals(op2num, OptEnum.OP_LEFT_BRACKET.getOpt())) {
// (
opStack.push(op2num);
} else if (Objects.equals(op2num, OptEnum.OP_RIGHT_BRACKET.getOpt())) {
// )
while (!opStack.peek().equals(OptEnum.OP_LEFT_BRACKET.getOpt())) {
resList.add(opStack.pop());
}
// 去掉 (
opStack.pop();
} else {
// 操作符 优先级
while (!CollectionUtils.isEmpty(opStack)
&& OptEnum.getEnum(opStack.peek()).getOptPriority() >= OptEnum.getEnum(op2num).getOptPriority()) {
resList.add(opStack.pop());
}
// 将 操作符 最后加入
opStack.push(op2num);
}
}
// 处理剩余的操作符
while (!CollectionUtils.isEmpty(opStack)) {
resList.add(opStack.pop());
}
return resList;
}
/**
* 判断是否数字
*
* @param numberStr
* @return
*/
public static boolean isNumber(String numberStr) {
if (numberStr == null) {
return false;
}
return numberStr.matches(NUM_REGEX);
}
/**
* 运算符 枚举
*/
public enum OptEnum {
/**
* 运算符
*/
OP_LEFT_BRACKET("(", "左括号", 0, null),
OP_RIGHT_BRACKET(")", "右括号", 7, null),
OP_ADD("+", "加", 2, (p1, p2) -> p1.add(p2)),
OP_SUB("-", "减", 2, (p1, p2) -> p1.subtract(p2)),
OP_MULTIPLY("*", "乘", 3, (p1, p2) -> p1.multiply(p2)),
OP_DIVIDE("/", "除", 3, (p1, p2) -> p1.divide(p2, CAL_SCALE_SIZE, CAL_SCALE_ROUND)),
OP_FLOOR("_", "取整", 3, (p1, p2) -> p1.divide(p2, 0, BigDecimal.ROUND_DOWN)),
OP_MODE("%", "取模", 3, (p1, p2) -> p1.divideAndRemainder(p2)[1]),
;
/**
* 运算符
*/
private String opt;
/**
* 运算说明
*/
private String optName;
/**
* 运算级别
*/
private Integer optPriority;
/**
* 运算 函数
*/
private BiFunction<BigDecimal, BigDecimal, BigDecimal> calFunction;
/**
* @param opt
* @param optName
* @param optPriority
* @param biFunction
*/
OptEnum(String opt, String optName, Integer optPriority
, BiFunction<BigDecimal, BigDecimal, BigDecimal> biFunction) {
this.opt = opt;
this.optName = optName;
this.optPriority = optPriority;
this.calFunction = biFunction;
}
/**
* 取枚举
*
* @param opt
* @return
*/
protected static OptEnum getEnum(String opt) {
return Stream.of(OptEnum.values())
.filter(t -> Objects.equals(opt, t.getOpt()))
.findFirst().orElseThrow(() -> new RuntimeException("运算符不支持"));
}
public String getOpt() {
return opt;
}
public String getOptName() {
return optName;
}
public Integer getOptPriority() {
return optPriority;
}
public BiFunction<BigDecimal, BigDecimal, BigDecimal> getCalFunction() {
return calFunction;
}
}
/**
* 校验 & 整理字符串
* 科学计数处理
*
* @param calExpressionStr
* @return
*/
private static String check2RepairExpression(String calExpressionStr) {
// 非空校验
if (StringUtils.isBlank(calExpressionStr)) {
throw new IllegalArgumentException("表达式不能为空!");
}
// 表达式字符合法性校验
if (!calExpressionStr.matches(EXPRESSION_PATTERN_REGEX)) {
throw new IllegalArgumentException("表达式含有非法字符!" + calExpressionStr);
}
// 整理字符串
calExpressionStr = calExpressionStr.replaceAll(SPACE, "");
// (- 替换为 (0-
calExpressionStr = calExpressionStr.replace(OptEnum.OP_LEFT_BRACKET.getOpt() + OptEnum.OP_SUB.getOpt()
, OptEnum.OP_LEFT_BRACKET.getOpt() + BigDecimal.ZERO + OptEnum.OP_SUB.getOpt());
// - 开始 前缀 0-
if (calExpressionStr.startsWith(OptEnum.OP_SUB.getOpt())) {
calExpressionStr = BigDecimal.ZERO + calExpressionStr;
}
calExpressionStr = calExpressionStr.replaceAll(ADD_SPACE_REGEX, " $1 ").trim();
if (logger.isDebugEnabled()) {
logger.info("整理后的运算串:{}", calExpressionStr);
}
return calExpressionStr;
}
public static void main(String[] args) throws Exception {
String str = "3.E+3 \n* 2";
boolean number = isNumber("3.E+3");
BigDecimal bigDecimal = executeExpression(str);
System.out.println(bigDecimal);
System.out.println(executeExpression("-1 + (-1) + 2"));
System.out.println(executeExpression(" 2 + 13/3 * 3 - 4 *(2 + 5 -2*4/2+9) + 3 + (2*1)-3"));
System.out.println(executeExpression("330000000000*121000000000000000000000000000"
, 4, BigDecimal.ROUND_HALF_UP));
System.out.println(executeExpression("9.2 *(20-1)-1+199 / 13"
, 4, BigDecimal.ROUND_HALF_UP));
System.out.println(executeExpression("9.2 *(20-1)-1+199 / 13"));
System.out.println(executeExpression("11"));
}
}