BigDecimal精度与相等比较的坑
先想一下,创建BigDecimal对象的时候一般是怎么创建的?
- new一个,传进去值
- BigDecimal.valueOf方法,传进去值
作为一个数字类型,经常有的操作是比较大小,有一种情况是比较是否相等。用equal方法还是compareTo方法?这里就是一个大坑
1 //new 传进去一个double 2 BigDecimal newZero = new BigDecimal(0.0); 3 System.out.println(BigDecimal.ZERO.equals(newZero)); 4 5 //new 传进去一个字符串 6 BigDecimal stringNewZero = new BigDecimal("0.0"); 7 System.out.println(BigDecimal.ZERO.equals(stringNewZero)); 8 9 //valueOf 传进去一个double 10 BigDecimal noScaleZero = BigDecimal.valueOf(0.0); 11 System.out.println(BigDecimal.ZERO.equals(noScaleZero)); 12 13 //valueOf 传进去一个double,再手动设置精度为1 14 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1); 15 System.out.println(BigDecimal.ZERO.equals(scaleZero));
用于比较的值全都是0,猜一猜上面几个equals方法返回的结果是什么?全都是true?no no no...
true false false false
惊不惊喜,意不意外?原因是什么呢?看一下BigDecimal的equals方法的实现:
1 public boolean equals(Object x) { 2 //类型不同,直接返回false 3 if (!(x instanceof BigDecimal)) 4 return false; 5 BigDecimal xDec = (BigDecimal) x; 6 //同一个对象,直接返回true 7 if (x == this) 8 return true; 9 //精度不同,直接返回false!! 10 if (scale != xDec.scale) 11 return false; 12 long s = this.intCompact; 13 long xs = xDec.intCompact; 14 if (s != INFLATED) { 15 if (xs == INFLATED) 16 xs = compactValFor(xDec.intVal); 17 return xs == s; 18 } else if (xs != INFLATED) 19 return xs == compactValFor(this.intVal); 20 21 return this.inflated().equals(xDec.inflated()); 22 }
从前面三个简单的判断就可以看出来,debug跟一下就知道是上面equals方法有三个返回false,都是因为精度不同。那么BigDecimal.ZERO的精度是多少呢?看下源码:
1 // Cache of common small BigDecimal values. 2 private static final BigDecimal zeroThroughTen[] = { 3 new BigDecimal(BigInteger.ZERO, 0, 0, 1), 4 new BigDecimal(BigInteger.ONE, 1, 0, 1), 5 new BigDecimal(BigInteger.valueOf(2), 2, 0, 1), 6 new BigDecimal(BigInteger.valueOf(3), 3, 0, 1), 7 new BigDecimal(BigInteger.valueOf(4), 4, 0, 1), 8 new BigDecimal(BigInteger.valueOf(5), 5, 0, 1), 9 new BigDecimal(BigInteger.valueOf(6), 6, 0, 1), 10 new BigDecimal(BigInteger.valueOf(7), 7, 0, 1), 11 new BigDecimal(BigInteger.valueOf(8), 8, 0, 1), 12 new BigDecimal(BigInteger.valueOf(9), 9, 0, 1), 13 new BigDecimal(BigInteger.TEN, 10, 0, 2), 14 }; 15 16 17 /** 18 * The value 0, with a scale of 0. 19 * 20 * @since 1.5 21 */ 22 public static final BigDecimal ZERO = zeroThroughTen[0];
BigDecimal.ZERO值为0,精度为0.
而上面几种返回false的case,都是因为精度不同。精度不同的原因,则是BigDecimal对象初始化的方式不同,从源码上看,前三种初始化的方式都不同。
所以说,BigDecimal比较大小,还是用compareTo方法比较靠谱,改为compareTo之后,上面四个case返回的结果都是相等:
1 BigDecimal newZero = new BigDecimal(0.0); 2 System.out.println(BigDecimal.ZERO.compareTo(newZero)); 3 4 BigDecimal stringNewZero = new BigDecimal("0.0"); 5 System.out.println(BigDecimal.ZERO.compareTo(stringNewZero)); 6 7 BigDecimal noScaleZero = BigDecimal.valueOf(0.0); 8 System.out.println(BigDecimal.ZERO.compareTo(noScaleZero)); 9 10 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1); 11 System.out.println(BigDecimal.ZERO.compareTo(scaleZero));
输出结果
0 0 0 0
由此联想到的一个更大的坑是,如果将BigDecimal的值作为HashMap的key,因为精度的问题,相同的值就可能出现hashCode值不同并且equals方法返回false,导致put和get就很可能会出现相同的值但是存取了不同的value。
再想一想,小数类型在计算机中本来就不能精确存储,再把其作为HashMap的key就相当不靠谱了,以后还是少用。
另外需要注意的一点是,写代码调别人写的方法时,最好是点进去看一下实现。再小再常用的方法,都可能埋着大坑
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了