支持直接套入计算公式的计算工具类CalcUtil

/**
 * 计算工具类
 */
@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);
        }

    }

}

觉得内容还不错?打赏个钢镚鼓励鼓励!!👍