[Java]BigDecimal与Double的区别和使用场景
BigDecimal与Double的区别和使用场景
背景
在项目中发现开发小组成员在写程序时,对于Oracle数据类型为Number的字段(经纬度),实体映射类型有的人用Double有的人用BigDecimal,没有一个统一规范,为此我在这里总结记录一下。
一般说到BigDecimal与Double,绕不开的就是金融或电商行业,毕竟涉及到了钱的问题,数据的敏感程度很高,对数据精度要求也很高。
BigDecimal与Double于两种类型在使用上都有一些缺点。
Double的问题
- 在计算时会出现不精确的问题
public static void main(String[] args) {
System.out.println(12.3 + 45.6); // 57.900000000000006
System.out.println(12.3 / 100); // 0.12300000000000001
}
小数部分无法使用二进制准确的表示
等于判断在使用时需要注意
public static void main(String[] args) {
double a = 2.111111111111111111111111112;
double b = 2.111111111111111111111111113;
// duoble超过15位以后就会不对了
System.out.println(a == b); // true
}
BigDecimal的问题
- 使用除法时除不尽会报
ArithmeticException异常
public static void main(String[] args) {
// 报异常:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3)));
}
public static void main(String[] args) {
// 需要指定精度和舍入方式,当除不尽时也不会报异常
// 运行结果为 40.33
System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP));
}
new BigDecimal(double)结果或许预料不到。
public static void main(String[] args) {
BigDecimal a = new BigDecimal(12.3);
BigDecimal b = BigDecimal.valueOf(12.3);
// Double的小数位其实无法被精确表示,所以传入的.3被精度扩展之后精度丢失,展示出来的并不是精确的.3
// 结果为12.300000000000000710542735760100185871124267578125
System.out.println(a);
// 从底层代码可以看出来,BigDecimal.valueOf 会先转换为字符串之后再调用new BigDecimal,不会造成精度丢失
// 结果为12.3
System.out.println(b);
}
所以一般情况下会使用BigDecimal.valueOf()而不是new BigDecimal()。
另:
BigDecimal.valueOf()是静态工厂类,永远优先于构造函数。(摘自《Effective java》)
BigDecimal是不可变类
不可变类代表着,任何针对BigDecimal的修改都将产生新对象。所以每次对BigDecimal的修改都要重新指向一个新的对象。
public static void main(String[] args) {
BigDecimal a = BigDecimal.valueOf(12.3);
a.add(BigDecimal.valueOf(2.1));
System.out.println(a); // 12.3,值未修改
BigDecimal b = BigDecimal.valueOf(12.3);
// 重新赋值给新对象
BigDecimal c = b.add(BigDecimal.valueOf(2.1));
System.out.println(c); // 14.4
}
- 比较大小不方便
BigDecimal大小的比较都需要使用compareTo,如果需要返回更大的数或更小的数可以使用max、min。还要注意在BigDecimal中慎用equals。
public static void main(String[] args) {
BigDecimal a = BigDecimal.valueOf(12.3);
BigDecimal b = BigDecimal.valueOf(12.32);
System.out.println(a.compareTo(b)); // -1
System.out.println(b.compareTo(a)); //1
System.out.println(a.max(b)); // 12.32
System.out.println(a.min(b)); // 12.3
System.out.println(b.max(a)); // 12.32
System.out.println(b.min(a)); // 12.3
System.out.println(BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0))); //false
}
BigDecimal重写了equals方法,在equals方法里比较了小数位数,在方法注释上也有说明,所以 BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0)) 为什么结果为false就可以理解了。在附录中会贴出 BigDecimal 中的 equals方法的源码。
通过源码可以知道 if (scale != xDec.scale) 这句代码就是比较了小数位数,不等则直接返回false。
Double与BigDecimal数值操作效率比较
做了一个测试,从1累加到1000000,Double与BigDecimal的效率比:
public static void main(String[] args) {
double a = 0;
long startLong = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
a += i;
}
// 打印结果:double耗时:9
System.out.println("double耗时:" + (System.currentTimeMillis() - startLong));
BigDecimal b = new BigDecimal(0);
long startLong2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
b = b.add(BigDecimal.valueOf(i));
}
// 打印结果:BigDecimal耗时:83
System.out.println("BigDecimal耗时:" + (System.currentTimeMillis() - startLong2));
}
可以看到Double比BigDecimal的效率高了快10倍。
总结
使用
Double:在计算过程中会出现精度丢失问题,但使用方便,计算效率高,在对精度要求不高的情况下建议使用。
使用
BigDecimal:高精度,但做除法的时候要注意除不尽异常,且因为是不可变类和对象类型,做计算时没那么方便,效率比
Double低。
所以可以知道,在使用场景上,如果涉及到精确的数值计算,比如典型的金额,一定要使用BigDecimal进行计算,对精准度要求没那么高的可以不使用BigDecimal。需要进行频繁计算的可以使用Double。
其实Double或者Float这种浮点类型如果不参与计算只是传值的话,其实没有精度问题,如果要计算就得注意一下。
那么回到我们自己的项目上,经纬度其实一般业务代码也不会去进行计算,大多是用于传参定位记录用,涉及到计算的比如距离,如果是保留到6位小数时已经是1米级别了,可以满足绝大多数场景了,所以在经纬度上使用Double就足够了。
附录
BigDecimal 的 equals方法源码。
/**
* Compares this {@code BigDecimal} with the specified
* {@code Object} for equality. Unlike {@link
* #compareTo(BigDecimal) compareTo}, this method considers two
* {@code BigDecimal} objects equal only if they are equal in
* value and scale (thus 2.0 is not equal to 2.00 when compared by
* this method).
* 该方法认为两个BigDecimal对象只有在值和比例相等时才相等,所以当使用该方法比较2.0与2.00时,二者不相等。
*
* @param x {@code Object} to which this {@code BigDecimal} is
* to be compared.
* @return {@code true} if and only if the specified {@code Object} is a
* {@code BigDecimal} whose value and scale are equal to this
* {@code BigDecimal}'s.
* @see #compareTo(java.math.BigDecimal)
* @see #hashCode
*/
@Override
public boolean equals(Object x) {
// 比较对象是否为 BigDecimal 数据类型,不是直接返回false
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
// 比较 scale 值是否相等。在这里比较了小数位数,不等返回false。
// scale 是BigDecimal 的标度。如果为零或正数,则标度是小数点后的位数。
// 如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。例如,-3 标度是指非标度值乘以 1000。
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
关于MySql中如何选用这两种类型
在查资料的时候还看到了关于MySql中如何选用这两种类型的问题,也在此记录一下。
在数据库中除了指定数据类型之外还需要指定精度,所以在MySql里Double的计算精度丢失比在Java里要高很多,Java的默认精度到了15-16位。
在阿里的编码规范中也强调统一带小数类型的一律要使用Decimal类型而不是Double,使用Decimal可以大大减少计算采坑的概率。
所以在选用类型时,与Java同样,在精度要求不高的情况下可以使用Double,比如经纬度,但是有需要计算、金融金额等优先使用Decimal。
参考链接:
[Java]BigDecimal与Double的区别和使用场景的更多相关文章
- java bigDecimal and double
Java BigDecimal和double BigDecimal是Java中用来表示任意精确浮点数运算的类,在BigDecimal中,使用unscaledValue × 10-scale来表示一 ...
- Java BigDecimal和double
BigDecimal类 对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数 ...
- Java BigDecimal和double BigDecimal类
BigDecimal类 对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数 ...
- Java中NIO和IO区别和适用场景
NIO是为了弥补IO操作的不足而诞生的,NIO的一些新特性有:非阻塞I/O,选择器,缓冲以及管道.管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征. 概念解释: ...
- Java线程池种类、区别和适用场景
newCachedThreadPool: 底层:返回ThreadPoolExecutor实例,corePoolSize为0:maximumPoolSize为Integer.MAX_VALUE:keep ...
- Java浮点数float,bigdecimal和double精确计算的精度误差问题总结
(转)Java浮点数float,bigdecimal和double精确计算的精度误差问题总结 1.float整数计算误差 案例:会员积分字段采用float类型,导致计算会员积分时,7位整数的数据计算结 ...
- MySQL中Decimal类型和Float Double的区别 & BigDecimal与Double使用场景
MySQL中存在float,double等非标准数据类型,也有decimal这种标准数据类型. 其区别在于,float,double等非标准类型,在DB中保存的是近似值,而Decimal则以字符串的形 ...
- java 金额计算,商业计算 double不精确问题 BigDecimal,Double保留两位小数方法
解决办法================== http://blog.javaxxz.com/?p=763 一提到Java里面的商业计算,我们都知道不能用float和double,因为他们无法 进行精 ...
- java中如何使用BigDecimal使得Double类型保留两位有效数字
一.场景:从数据表中读出Decimal类型的数据直接塞给Double类型的对象时,并不会有什么异常. 如果要再此基础上计算,就会发生异常. 比如:读出数据为0.0092,将其乘以100,则变成了0.9 ...
- Java BigDecimal 转换,除法陷阱(转)
源地址: http://blog.csdn.net/niannian_315/article/details/24354251 今天在用BigDecimal“出现费解”现象,以前虽然知道要避免用, ...
随机推荐
- C#设计模式08——桥接模式的写法
什么是C#桥接模式?桥接模式是一种结构型设计模式,它可以将抽象部分与实现部分分离,使它们可以独立地变化.这种模式通过将实现细节从抽象类中分离出来,从而让它们可以根据需要独立变化. 为什么要使用C#桥接 ...
- Go 语言中 defer 使用时有哪些陷阱?
大家好,我是 frank ,「 Golang 语言开发栈」公众号作者. 01 介绍 defer 的使用方式是在其后紧跟一个函数调用或方法调用,确保在其所在的函数体返回之前执行其调用的函数或方法. 在 ...
- Guava缓存工具类封装和使用
本文为博主原创,未经允许不得转载: Guava是谷歌提供的一款强大的java工具库,里面包含了很多方便且高效的工具,在项目开发中有业务场景需要保存数据到内存当中, 且只需要保存固定时间就可以,该数据只 ...
- jvisualm 结合 visualGC 进行jvm监控,并分析垃圾回收
本文为博主原创,未经允许不得转载 1.jvisualm 的使用 打开 jdk 安装目录bin目录下的 jvisualvm.exe 工具 2. visual GC插件的安装及监控分析 Visual GC ...
- linux 查看服务器cpu 与内存配置
转载请注明出处: 1. 通过 lscpu 命令查看 服务器的cpu 配置 显示格式: Architecture: #架构 CPU(s): #逻辑cpu颗数 Thread(s) per core: #每 ...
- 23- 数码管动态显示02-转换BCD码
1.BCD码 数码管动态显示的data[19:0]使用二进制数表示的多位十进制数,不能直接生成段选和片选信号,需要使用BCD码表示的十进制数 BCD码(Binary-Coded Decimal),又称 ...
- Linux-搜索-文件-find-locate-内容过滤-grep
- Go-获取密码的sha值
// 对用户密码进行加密 func EncodePwd(pwd string) string { s := sha256.New() s.Write([]byte(pwd)) data := s.Su ...
- 使用Java分析器优化代码性能,解决OOM问题
有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 背景 最近我一直在做性能优化,对一个单机应用做性能优化.主要是 ...
- [转帖]windows10彻底关闭Windows Defender的4种方法
https://zhuanlan.zhihu.com/p/495107049 Windows Defender是windows10系统自带的杀毒软件.默认情况下它处于打开的状态.大多数第三方的杀毒软件 ...