高精度运算工具类ArithTool 背景为什么要用高精度运算在Java中使用double或float进行浮点数运算时经常会遇到精度丢失的问题。这是因为计算机使用二进制存储浮点数而某些十进制小数如0.1无法被二进制精确表示。经典精度问题示例System.out.println(0.1 0.2); // 输出: 0.30000000000000004 System.out.println(1.0 - 0.9); // 输出: 0.09999999999999998 System.out.println(0.3 / 0.1); // 输出: 2.9999999999999996在金融计算、科学计算等场景下这种精度误差是不可接受的。BigDecimal类提供了任意精度的定点数表示是解决这一问题的标准方案。️ 完整代码import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Objects; /** * 高精度运算工具类 * * p提供精确的浮点数运算包括加减乘除和四舍五入。 * 基于 {link BigDecimal} 实现避免 double/float 的精度丢失问题。/p * * pstrong适用场景/strong/p * ul * li金融金额计算价格、金额、汇率/li * li科学计算需要高精度结果/li * li任何不允许精度误算的数值运算/li * /ul * * pstrong使用示例/strong/p * pre * double result ArithTool.add(0.1, 0.2); // 结果: 0.3 * double price ArithTool.mul(19.99, 3); // 结果: 59.97 * double avg ArithTool.div(10, 3, 2); // 结果: 3.33保留2位小数 * /pre * * author lchhh0005 * version 2.0 */ public final class ArithTool { /** 默认除法运算精度小数点后10位 */ private static final int DEFAULT_DIV_SCALE 10; /** 私有构造方法防止实例化 */ private ArithTool() { throw new AssertionError(工具类禁止实例化); } /** * 精确的加法运算 * * p将两个 double 数值转换为 {link BigDecimal} 后相加 * 最后返回 double 类型的精确结果。/p * * param v1 被加数 * param v2 加数 * return 两个参数的和 * throws NumberFormatException 如果参数为 NaN 或 Infinity */ public static double add(double v1, double v2) { BigDecimal b1 convertToBigDecimal(v1); BigDecimal b2 convertToBigDecimal(v2); return b1.add(b2).doubleValue(); } /** * 精确的减法运算 * * param v1 被减数 * param v2 减数 * return 两个参数的差v1 - v2 * throws NumberFormatException 如果参数为 NaN 或 Infinity */ public static double subtract(double v1, double v2) { BigDecimal b1 convertToBigDecimal(v1); BigDecimal b2 convertToBigDecimal(v2); return b1.subtract(b2).doubleValue(); } /** * 精确的乘法运算 * * param v1 被乘数 * param v2 乘数 * return 两个参数的积 * throws NumberFormatException 如果参数为 NaN 或 Infinity */ public static double multiply(double v1, double v2) { BigDecimal b1 convertToBigDecimal(v1); BigDecimal b2 convertToBigDecimal(v2); return b1.multiply(b2).doubleValue(); } /** * 精确的除法运算默认精度 * * p当发生除不尽的情况时精确到小数点后10位之后的数字四舍五入。/p * * param v1 被除数 * param v2 除数 * return 两个参数的商 * throws ArithmeticException 如果除数为0 * throws IllegalArgumentException 如果参数为 NaN 或 Infinity */ public static double divide(double v1, double v2) { return divide(v1, v2, DEFAULT_DIV_SCALE); } /** * 精确的除法运算指定精度 * * p当发生除不尽的情况时由 scale 参数指定精度之后的数字四舍五入。/p * * param v1 被除数 * param v2 除数 * param scale 小数点后保留的位数必须 0 * return 两个参数的商 * throws ArithmeticException 如果除数为0 * throws IllegalArgumentException 如果 scale 0或参数为 NaN/Infinity */ public static double divide(double v1, double v2, int scale) { validateScale(scale); validateDivisor(v2); BigDecimal b1 convertToBigDecimal(v1); BigDecimal b2 convertToBigDecimal(v2); return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); } /** * 四舍五入处理 * * p对指定数值进行四舍五入保留指定小数位数。/p * * param v 需要四舍五入的数字 * param scale 小数点后保留的位数必须 0 * return 四舍五入后的结果 * throws IllegalArgumentException 如果 scale 0或参数为 NaN/Infinity */ public static double round(double v, int scale) { validateScale(scale); BigDecimal b convertToBigDecimal(v); return b.setScale(scale, RoundingMode.HALF_UP).doubleValue(); } /** * 将 double 安全转换为 BigDecimal * * p使用 {link Double#toString(double)} 而非 {link BigDecimal#valueOf(double)} * 或直接构造避免浮点表示误差被保留。/p * * param value 待转换的 double 值 * return 对应的 BigDecimal 对象 * throws IllegalArgumentException 如果值为 NaN 或 Infinity */ private static BigDecimal convertToBigDecimal(double value) { if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException( 非法数值: value 不接受 NaN 或 Infinity ); } return new BigDecimal(Double.toString(value)); } /** * 验证精度参数 * * param scale 待验证的精度值 * throws IllegalArgumentException 如果 scale 0 */ private static void validateScale(int scale) { if (scale 0) { throw new IllegalArgumentException( 精度必须为非负整数当前值: scale ); } } /** * 验证除数不为零 * * param divisor 除数 * throws ArithmeticException 如果除数为0 */ private static void validateDivisor(double divisor) { if (divisor 0.0) { throw new ArithmeticException(除数不能为0); } } } 主要优化点说明1.命名规范化原命名优化后说明subsubtract使用完整单词提高可读性mulmultiply同上divdivide同上DEF_DIV_SCALEDEFAULT_DIV_SCALE避免缩写语义更清晰2.常量命名与访问控制// 原代码 private static final int DEF_DIV_SCALE 10; // 优化后 private static final int DEFAULT_DIV_SCALE 10;使用全大写下划线的标准常量命名保持private访问控制防止外部修改3.构造方法强化// 原代码可被反射实例化 private ArithTool() { } // 优化后彻底防止实例化 private ArithTool() { throw new AssertionError(工具类禁止实例化); }4.BigDecimal.ROUND_HALF_UP 已废弃// 原代码已废弃 BigDecimal.ROUND_HALF_UP // 优化后推荐用法 RoundingMode.HALF_UP⚠️重要BigDecimal.ROUND_HALF_UP在 Java 9 中已被标记为Deprecated应使用RoundingMode.HALF_UP枚举替代。5.四舍五入方法优化// 原代码绕弯实现 BigDecimal one new BigDecimal(1); return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); // 优化后直接设置精度 return b.setScale(scale, RoundingMode.HALF_UP).doubleValue();原代码通过除以1来实现四舍五入虽然结果正确但逻辑不够直观。使用setScale()方法语义更清晰。6.参数校验增强// 新增除数为0的校验 private static void validateDivisor(double divisor) { if (divisor 0.0) { throw new ArithmeticException(除数不能为0); } } // 新增 NaN/Infinity 校验 if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException(非法数值...); }7.异常信息中文化原代码使用英文异常信息优化后改为中文更符合国内团队开发习惯// 原代码 throw new IllegalArgumentException(The scale must be a positive integer or zero); // 优化后 throw new IllegalArgumentException(精度必须为非负整数当前值: scale); 使用示例public class Demo { public static void main(String[] args) { // 1. 加法 - 解决 0.1 0.2 ! 0.3 问题 double sum ArithTool.add(0.1, 0.2); System.out.println(0.1 0.2 sum); // 输出: 0.3 // 2. 减法 double diff ArithTool.subtract(1.0, 0.9); System.out.println(1.0 - 0.9 diff); // 输出: 0.1 // 3. 乘法 - 金额计算 double total ArithTool.multiply(19.99, 3); System.out.println(19.99 × 3 total); // 输出: 59.97 // 4. 除法 - 默认精度10位 double result1 ArithTool.divide(10, 3); System.out.println(10 ÷ 3 result1); // 输出: 3.3333333333 // 5. 除法 - 指定精度2位适合金额 double result2 ArithTool.divide(10, 3, 2); System.out.println(10 ÷ 3 (保留2位) result2); // 输出: 3.33 // 6. 四舍五入 double rounded ArithTool.round(3.14159, 2); System.out.println(3.14159 保留2位 rounded); // 输出: 3.14 } }⚡ 进阶建议如果需要支持更多数据类型当前工具类只支持double类型。实际业务中可能需要支持String、BigDecimal等类型可以扩展如下// 支持 String 类型避免 double 构造时的精度问题 public static double add(String v1, String v2) { BigDecimal b1 new BigDecimal(v1); BigDecimal b2 new BigDecimal(v2); return b1.add(b2).doubleValue(); } // 直接返回 BigDecimal避免转回 double 再次丢失精度 public static BigDecimal add(BigDecimal v1, BigDecimal v2) { return v1.add(v2); }如果用于金融系统金融计算建议全程使用BigDecimal最后一步才转换为double如果需要或者直接以BigDecimal返回// 金融场景推荐返回 BigDecimal避免多次类型转换 public static BigDecimal multiply(BigDecimal v1, BigDecimal v2) { return v1.multiply(v2); }