Skip to content
On this page

Java 中金额的计算

推荐使用 BigDecimal

Java 的 Math 类(比如 Math.round())虽然方便,但解决不了精度问题,它只是帮你做取整,而不是解决浮点数本身精度丢失的问题。

为什么 BigDecimal 是解法?

因为 BigDecimal 使用十进制表示数字,而不是二进制,所以能精确表示 0.1、0.2 这样的小数,而不会像 float/double 那样变成 0.10000000000000000555...

通用工具类推荐

java
/**
 * 精确计算工具类 - 解决Java浮点数精度丢失问题
 * 支持 null 安全处理,所有方法都考虑了 null 参数的情况
 */
public class BigDecimalUtil {

    // 默认精度(小数点后2位,常用在金融计算)
    private static final int DEFAULT_SCALE = 2;
    // 默认舍入模式(四舍五入)
    private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_UP;
    // 默认值(用于处理 null 参数)
    private static final BigDecimal DEFAULT_VALUE = BigDecimal.ZERO;

    // 私有构造方法,防止实例化
    private BigDecimalUtil() {
        throw new AssertionError("不能实例化工具类");
    }

    /**
     * 安全转换为 BigDecimal
     */
    public static BigDecimal safe(Object value) {
        return safe(value, DEFAULT_VALUE);
    }

    public static BigDecimal safe(Object value, BigDecimal defaultValue) {
        if (value == null) {
            return defaultValue;
        }

        try {
            if (value instanceof BigDecimal) {
                return (BigDecimal) value;
            } else if (value instanceof Integer) {
                return BigDecimal.valueOf((Integer) value);
            } else if (value instanceof Long) {
                return BigDecimal.valueOf((Long) value);
            } else if (value instanceof Double) {
                return BigDecimal.valueOf((Double) value);
            } else if (value instanceof String) {
                String str = ((String) value).trim();
                return str.isEmpty() ? defaultValue : new BigDecimal(str);
            } else if (value instanceof Number) {
                return new BigDecimal(value.toString());
            } else {
                return defaultValue;
            }
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    /**
     * 精确加法(支持 null 安全)
     */
    public static BigDecimal add(Object v1, Object v2) {
        return safe(v1).add(safe(v2));
    }

    public static BigDecimal add(Object v1, Object v2, Object v3) {
        return safe(v1).add(safe(v2)).add(safe(v3));
    }

    public static BigDecimal add(Object... values) {
        BigDecimal result = BigDecimal.ZERO;
        for (Object value : values) {
            result = result.add(safe(value));
        }
        return result;
    }

    /**
     * 精确减法(支持 null 安全)
     */
    public static BigDecimal subtract(Object v1, Object v2) {
        return safe(v1).subtract(safe(v2));
    }

    /**
     * 精确乘法(支持 null 安全)
     */
    public static BigDecimal multiply(Object v1, Object v2) {
        return safe(v1).multiply(safe(v2));
    }

    public static BigDecimal multiply(Object v1, Object v2, Object v3) {
        return safe(v1).multiply(safe(v2)).multiply(safe(v3));
    }

    /**
     * 精确除法(支持 null 安全)
     *  @param dividend 被除数
     *  @param divisor 除数
     *  @return 两个数的商
     */
    public static BigDecimal divide(Object dividend, Object divisor ) {
        return divide(dividend, divisor , DEFAULT_SCALE, DEFAULT_ROUNDING);
    }

    public static BigDecimal divide(Object v1, Object v2, int scale) {
        return divide(v1, v2, scale, DEFAULT_ROUNDING);
    }

    public static BigDecimal divide(Object v1, Object v2, int scale, RoundingMode roundingMode) {
        BigDecimal divisor = safe(v2);
        if (divisor.compareTo(BigDecimal.ZERO) == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return safe(v1).divide(divisor, scale, roundingMode);
    }

    /**
     * 安全除法(除数为零时返回默认值)
     */
    public static BigDecimal safeDivide(Object v1, Object v2, BigDecimal defaultValue) {
        BigDecimal divisor = safe(v2);
        if (divisor.compareTo(BigDecimal.ZERO) == 0) {
            return defaultValue;
        }
        return safe(v1).divide(divisor, DEFAULT_SCALE, DEFAULT_ROUNDING);
    }

    public static BigDecimal safeDivide(Object v1, Object v2) {
        return safeDivide(v1, v2, DEFAULT_VALUE);
    }

    /**
     * 比较两个数值
     */
    public static boolean isGreaterThan(Object v1, Object v2) {
        return safe(v1).compareTo(safe(v2)) > 0;
    }

    public static boolean isLessThan(Object v1, Object v2) {
        return safe(v1).compareTo(safe(v2)) < 0;
    }

    public static boolean equals(Object v1, Object v2) {
        return safe(v1).compareTo(safe(v2)) == 0;
    }

    public static boolean equals(Object v1, Object v2, int scale) {
        BigDecimal b1 = safe(v1).setScale(scale, DEFAULT_ROUNDING);
        BigDecimal b2 = safe(v2).setScale(scale, DEFAULT_ROUNDING);
        return b1.compareTo(b2) == 0;
    }

    /**
     * 格式化和舍入
     */
    public static BigDecimal round(Object value) {
        return round(value, DEFAULT_SCALE);
    }

    public static BigDecimal round(Object value, int scale) {
        return round(value, scale, DEFAULT_ROUNDING);
    }

    public static BigDecimal round(Object value, int scale, RoundingMode roundingMode) {
        return safe(value).setScale(scale, roundingMode);
    }

    public static String format(Object value) {
        return format(value, DEFAULT_SCALE);
    }

    public static String format(Object value, int scale) {
        return format(value, scale, DEFAULT_ROUNDING);
    }

    public static String format(Object value, int scale, RoundingMode roundingMode) {
        return round(value, scale, roundingMode).toString();
    }

    /**
     * 百分比计算
     */
    public static BigDecimal percentage(Object value) {
        return safe(value).multiply(new BigDecimal("100"));
    }

    public static BigDecimal calculatePercentage(Object part, Object total) {
        if (safe(total).compareTo(BigDecimal.ZERO) == 0) {
            return DEFAULT_VALUE;
        }
        return safe(part).multiply(new BigDecimal("100"))
                .divide(safe(total), DEFAULT_SCALE, DEFAULT_ROUNDING);
    }

    /**
     * 平均值计算
     */
    public static BigDecimal average(Object... values) {
        if (values == null || values.length == 0) {
            return DEFAULT_VALUE;
        }

        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;

        for (Object value : values) {
            if (value != null) {
                sum = sum.add(safe(value));
                count++;
            }
        }

        return count == 0 ? DEFAULT_VALUE :
                sum.divide(BigDecimal.valueOf(count), DEFAULT_SCALE, DEFAULT_ROUNDING);
    }

    /**
     * 最大值和最小值
     */
    public static BigDecimal max(Object v1, Object v2) {
        BigDecimal b1 = safe(v1);
        BigDecimal b2 = safe(v2);
        return b1.compareTo(b2) > 0 ? b1 : b2;
    }

    public static BigDecimal min(Object v1, Object v2) {
        BigDecimal b1 = safe(v1);
        BigDecimal b2 = safe(v2);
        return b1.compareTo(b2) < 0 ? b1 : b2;
    }

    /**
     * 验证是否为有效的数值
     */
    public static boolean isValidNumber(Object value) {
        try {
            safe(value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 判断是否为正数、负数或零
     */
    public static boolean isPositive(Object value) {
        return safe(value).compareTo(BigDecimal.ZERO) > 0;
    }

    public static boolean isNegative(Object value) {
        return safe(value).compareTo(BigDecimal.ZERO) < 0;
    }

    public static boolean isZero(Object value) {
        return safe(value).compareTo(BigDecimal.ZERO) == 0;
    }

    /**
     * 保持原有接口的兼容性
     */
    public static BigDecimal add(double v1, double v2) {
        return add(BigDecimal.valueOf(v1), BigDecimal.valueOf(v2));
    }

    public static BigDecimal add(String v1, String v2) {
        return add(safe(v1), safe(v2));
    }

    public static BigDecimal subtract(double v1, double v2) {
        return subtract(BigDecimal.valueOf(v1), BigDecimal.valueOf(v2));
    }

    public static BigDecimal subtract(String v1, String v2) {
        return subtract(safe(v1), safe(v2));
    }

    public static BigDecimal multiply(double v1, double v2) {
        return multiply(BigDecimal.valueOf(v1), BigDecimal.valueOf(v2));
    }

    public static BigDecimal multiply(String v1, String v2) {
        return multiply(safe(v1), safe(v2));
    }
}

  • 使用字符串构造:避免了 new BigDecimal(0.1)这种错误用法,直接用 new BigDecimal("0.1"),确保精度
  • 默认精度设置:金融计算通常需要保留 2 位小数,所以默认是 2 位
  • 舍入模式:默认使用 HALF_UP(四舍五入),符合大多数业务需求
  • 避免了 double 的精度问题:所有方法都推荐使用字符串形式

使用示例

java
// 正确做法:使用字符串形式
BigDecimal result = BigDecimalUtil.add("0.1", "0.2");
System.out.println(result); // 输出: 0.3

// 错误做法:使用double,会导致精度问题
BigDecimal wrongResult = new BigDecimal(0.1 + 0.2);
System.out.println(wrongResult); // 输出: 0.30000000000000004

注意事项

  • 不要用 double 构造 BigDecimal:new BigDecimal(0.1)会出问题,必须用 new BigDecimal("0.1")
  • 除法必须指定精度:divide()方法必须指定精度,否则可能抛出 ArithmeticException
  • 性能考虑:BigDecimal 比基本类型慢,但对精度要求高的场景(如金融)是值得的

上次更新于: