03 | BigDecimal 用 double 构造精度就这样丢了摘要new BigDecimal(0.1)得到的不是 0.1而是 0.100000000000000005551… 本文讲清 BigDecimal 的正确使用方式。一、问题现象publicclassBigDecimalTest{publicstaticvoidmain(String[]args){BigDecimalanewBigDecimal(0.1);BigDecimalbnewBigDecimal(0.1);System.out.println(a);// 0.1000000000000000055511151231257827021181583404541015625System.out.println(b);// 0.1System.out.println(a.equals(b));// false}}再看一个金融场景的灾难publicclassMoneyCalc{publicstaticvoidmain(String[]args){BigDecimalpricenewBigDecimal(19.99);// ❌ 用 double 构造BigDecimalquantitynewBigDecimal(3);BigDecimaltotalprice.multiply(quantity);System.out.println(total);// 59.970000000000001... 不是 59.97}}二、踩坑现场场景 1金额计算用了double构造// ❌ 错误电商订单金额计算BigDecimalorderAmountnewBigDecimal(199.99);BigDecimaltaxRatenewBigDecimal(0.06);BigDecimaltaxorderAmount.multiply(taxRate);// 预期11.9994实际各种奇怪的精度问题场景 2equals比较不忽略精度差异BigDecimalanewBigDecimal(1.0);BigDecimalbnewBigDecimal(1.00);System.out.println(a.equals(b));// false精度不同System.out.println(a.compareTo(b)0);// true正确场景 3除法不设置舍入模式BigDecimalanewBigDecimal(10);BigDecimalbnewBigDecimal(3);System.out.println(a.divide(b));// ❌ ArithmeticException: Non-terminating decimal expansion三、原理解析3.1 浮点数的本质缺陷double和float遵循IEEE 754 标准用二进制表示十进制小数而很多十进制小数无法用二进制精确表示。十进制 0.1 二进制 0.00011001100110011...无限循环 计算机存储0.1000000000000000055511...new BigDecimal(double)完全保留了这个不精确值把它精确地存了下来。3.2 三个构造方法对比构造方法行为推荐度new BigDecimal(double)保留 double 的全部不精确位❌ 禁止使用new BigDecimal(String)精确解析字符串✅ 强烈推荐BigDecimal.valueOf(double)内部先转成字符串再解析✅ 推荐// BigDecimal.valueOf() 源码publicstaticBigDecimalvalueOf(doubleval){returnnewBigDecimal(Double.toString(val));// 先转字符串再构造}3.3equalsvscompareTo// equals 比较值 精度scale完全一致才返回 trueBigDecimalanewBigDecimal(1.0);// scale 1BigDecimalbnewBigDecimal(1.00);// scale 2a.equals(b)// falsescale 不同// compareTo 比较只比较数值大小忽略精度a.compareTo(b)0// true数值相等结论比较数值大小时用compareTo不要用equals。3.4 八种舍入模式// BigDecimal 除法必须指定舍入模式BigDecimalanewBigDecimal(10);BigDecimalbnewBigDecimal(3);// ✅ 正确写法BigDecimalresulta.divide(b,2,RoundingMode.HALF_UP);System.out.println(result);// 3.33常用舍入模式模式说明场景RoundingMode.HALF_UP四舍五入金额计算最常用RoundingMode.HALF_DOWN五舍六入统计学RoundingMode.HALF_EVEN银行家舍入法金融专业场景RoundingMode.DOWN直接截断不四舍五入RoundingMode.UP只要有小数就进位保守计算四、正确写法4.1 金额计算永远用字符串构造// ✅ 正确写法BigDecimalpricenewBigDecimal(19.99);BigDecimalquantitynewBigDecimal(3);BigDecimaltotalprice.multiply(quantity);System.out.println(total);// 59.97精确4.2 从double转换用valueOf// ✅ 如果源头就是 double比如第三方接口返回doublevalue19.99;BigDecimalbdBigDecimal.valueOf(value);// 内部会先做 Double.toString()4.3 比较大小用compareToBigDecimalanewBigDecimal(1.0);BigDecimalbnewBigDecimal(1.00);// ✅ 正确比较if(a.compareTo(b)0){System.out.println(相等);}4.4 完整工具类示例importjava.math.BigDecimal;importjava.math.RoundingMode;publicclassMoneyUtils{/** 默认精度金额用2位 */privatestaticfinalintDEFAULT_SCALE2;/** 金额相加 */publicstaticBigDecimaladd(Stringv1,Stringv2){returnnewBigDecimal(v1).add(newBigDecimal(v2));}/** 金额相减 */publicstaticBigDecimalsubtract(Stringv1,Stringv2){returnnewBigDecimal(v1).subtract(newBigDecimal(v2));}/** 金额相乘保留2位小数 */publicstaticBigDecimalmultiply(Stringv1,Stringv2){returnnewBigDecimal(v1).multiply(newBigDecimal(v2)).setScale(DEFAULT_SCALE,RoundingMode.HALF_UP);}/** 金额相除保留2位小数 */publicstaticBigDecimaldivide(Stringv1,Stringv2){returnnewBigDecimal(v1).divide(newBigDecimal(v2),DEFAULT_SCALE,RoundingMode.HALF_UP);}/** 比较相等忽略精度差异 */publicstaticbooleanequals(BigDecimalv1,BigDecimalv2){returnv1.compareTo(v2)0;}}五、最佳实践✅ 金额计算的 6 条军规禁止使用double/float做金额计算BigDecimal构造优先用字符串其次用valueOf(double)比较大小用compareTo别用equals除法必须指定精度和舍入模式运算结果用setScale明确精度避免隐式精度扩散数据库 Decimal 类型对应 JavaBigDecimal不要用Double接收 数据库字段类型对照数据库类型Java 类型说明DECIMAL(10,2)BigDecimal✅ 正确DECIMAL(10,2)Double❌ 精度丢失FLOAT/DOUBLEDouble⚠️ 仅限非金融场景️ 阿里巴巴 Java 开发手册规约【强制】禁止使用构造方法BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。优先推荐入参为 String 的构造方法或使用 BigDecimal 的valueOf方法。六、小结double本身不精确new BigDecimal(double)会把这种不精确精确地保留下来正确构造方式new BigDecimal(String)或BigDecimal.valueOf(double)equals比较精度compareTo比较数值金额比较用后者除法不设置舍入模式会抛ArithmeticException金融场景全程用BigDecimal精度用setScale显式控制下一篇预告String 背后发生了什么别让拼接拖垮性能—— 为什么循环里用拼接字符串是性能杀手以及编译器的优化边界在哪里。