【Java踩坑笔记】【基础语法篇】03_BigDecimal用double构造?精度就这样丢了
2026/6/25 12:03:30 网站建设 项目流程

03 | BigDecimal 用 double 构造?精度就这样丢了

摘要new BigDecimal(0.1)得到的不是 0.1,而是 0.100000000000000005551… 本文讲清 BigDecimal 的正确使用方式。


一、问题现象

publicclassBigDecimalTest{publicstaticvoidmain(String[]args){BigDecimala=newBigDecimal(0.1);BigDecimalb=newBigDecimal("0.1");System.out.println(a);// 0.1000000000000000055511151231257827021181583404541015625System.out.println(b);// 0.1System.out.println(a.equals(b));// false}}

再看一个金融场景的灾难:

publicclassMoneyCalc{publicstaticvoidmain(String[]args){BigDecimalprice=newBigDecimal(19.99);// ❌ 用 double 构造BigDecimalquantity=newBigDecimal("3");BigDecimaltotal=price.multiply(quantity);System.out.println(total);// 59.970000000000001... (不是 59.97!)}}

二、踩坑现场

场景 1:金额计算用了double构造

// ❌ 错误:电商订单金额计算BigDecimalorderAmount=newBigDecimal(199.99);BigDecimaltaxRate=newBigDecimal(0.06);BigDecimaltax=orderAmount.multiply(taxRate);// 预期:11.9994,实际:各种奇怪的精度问题

场景 2:equals比较不忽略精度差异

BigDecimala=newBigDecimal("1.0");BigDecimalb=newBigDecimal("1.00");System.out.println(a.equals(b));// false!精度不同System.out.println(a.compareTo(b)==0);// true,正确

场景 3:除法不设置舍入模式

BigDecimala=newBigDecimal("10");BigDecimalb=newBigDecimal("3");System.out.println(a.divide(b));// ❌ ArithmeticException: Non-terminating decimal expansion

三、原理解析

3.1 浮点数的本质缺陷

doublefloat遵循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)完全一致才返回 trueBigDecimala=newBigDecimal("1.0");// scale = 1BigDecimalb=newBigDecimal("1.00");// scale = 2a.equals(b)// false:scale 不同// compareTo 比较:只比较数值大小,忽略精度a.compareTo(b)==0// true:数值相等

结论:比较数值大小时用compareTo,不要用equals

3.4 八种舍入模式

// BigDecimal 除法必须指定舍入模式BigDecimala=newBigDecimal("10");BigDecimalb=newBigDecimal("3");// ✅ 正确写法BigDecimalresult=a.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 金额计算:永远用字符串构造

// ✅ 正确写法BigDecimalprice=newBigDecimal("19.99");BigDecimalquantity=newBigDecimal("3");BigDecimaltotal=price.multiply(quantity);System.out.println(total);// 59.97(精确)

4.2 从double转换:用valueOf

// ✅ 如果源头就是 double(比如第三方接口返回)doublevalue=19.99;BigDecimalbd=BigDecimal.valueOf(value);// 内部会先做 Double.toString()

4.3 比较大小:用compareTo

BigDecimala=newBigDecimal("1.0");BigDecimalb=newBigDecimal("1.00");// ✅ 正确比较if(a.compareTo(b)==0){System.out.println("相等");}

4.4 完整工具类示例

importjava.math.BigDecimal;importjava.math.RoundingMode;publicclassMoneyUtils{/** 默认精度(金额用2位) */privatestaticfinalintDEFAULT_SCALE=2;/** 金额相加 */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 条军规

  1. 禁止使用double/float做金额计算
  2. BigDecimal构造优先用字符串,其次用valueOf(double)
  3. 比较大小用compareTo,别用equals
  4. 除法必须指定精度和舍入模式
  5. 运算结果用setScale明确精度,避免隐式精度扩散
  6. 数据库 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 + 背后发生了什么?别让拼接拖垮性能—— 为什么循环里用+拼接字符串是性能杀手,以及编译器的优化边界在哪里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询