概述
文章目录
- 问题一:BigDecimal不能使用equals方法做等值比较
- 1.1、例子:使用equals()来比较BigDecimal
- 1.2、标度(精度)问题
- 1.3、例子:使用compareTo()来比较BigDecimal
- 问题二:BigDecimal使用double初始化时存在精度风险
- 2.1、BigDecimal如何精确计数?
- 2.2、BigDecimal(double)有什么问题
BigDecimal,是java.math包中提供的一种可以用来进行更高精度运算的类型,相较于double、float这些类型来说,BigDecimal在和金额计算打交道应该说有着天然的优势,接下来我们一起来分析下BigDecimal中的哪些注意事项
- BigDecimal不能使用equals方法做等值比较
- BigDecimal使用double初始化时存在精度风险
问题一:BigDecimal不能使用equals方法做等值比较
知道大家有没有注意到,在《阿里巴巴Java开发手册》中其实也有注明
在比较BigDecimal的时候,千万不要用 == 这种方式来,这个应该不用多说吧,BigDecimal属于对象,不是基本类型,不能用 == 来比较。
但是equals可以来比较对象,但是!用equals来比较BigDecimal 也有问题,因为我们的目的是比较数值的大小。
那该如何比较呢?自定义个类,继承BigDecimal,重写equals,当然可以。但是其实有更好的办法,在BigDecimal内部提供了compareTo
方法买这个方法可以直接判断两个数字的值,相等则返回0
1.1、例子:使用equals()来比较BigDecimal
我们来看个例子,使用equals来比较BigDecimal:
public static void main(String[] args) {
BigDecimal bigDecimal1 = new BigDecimal(1);
BigDecimal bigDecimal2 = new BigDecimal(1);
System.out.println(bigDecimal1.equals(bigDecimal2));
BigDecimal bigDecimal3 = new BigDecimal(1);
BigDecimal bigDecimal4 = new BigDecimal(1.0);
System.out.println(bigDecimal3.equals(bigDecimal4));
BigDecimal bigDecimal5 = new BigDecimal("1");
BigDecimal bigDecimal6 = new BigDecimal("1.0");
System.out.println(bigDecimal5.equals(bigDecimal6));
}
//输出结果:
true
true
false
看输出结果,有的时候结果是true,有的时候结果却是false,很奇怪,为什么呢?我们来看下BigDecimal的equals的源码:
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
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());
}
里面有一个scale标度(精度)的比较,大概这就是为什么bigDecimal5和bigDeclmal6的比较结果是false的原因了。equals不仅会比较数值,还会比较这个标度(精度)是否一样。依此,引申出标度(精度)问题,见下。
1.2、标度(精度)问题
BigDecimal使用equals进行比较的时候会比较数值大小和scale标度(精度)。
问题:那为什么上面的bigDecimal1和2、bigDecimal3和4却是相同的呢,难道是因为他们的类型是int、long,而bigDecimal5和6的类型是string,导致出现精度问题?
BigDecimal有四种定义的类型,包括int、long、double、String四种,首先int和long类型都是整数,标度都是0。当类型是double的时候,new Bigdecimal(double) => new BigDecimale(0.1),实际传入的是0.1000000000000000055511151231527827021181583404541015625,这个时候的标度(精度)就是55,也就是小数点后面的个数。
而对于 new BigDecimal(1.0)来说,实际上就是整数,也就是不存在后缀,所以和整数的标度大小是一样的。
对于BigDecimal(String)来说,当我们传入一个字符串的时候,如:new BigDecimal(“0.1”) 创建一个BigDecimal的时候,其实创建出来的值正好就是等于0.1的,那么他的标度(精度)也就是1。如果使用的是new BigDecimal(“0.10000”),此时标度就是5,所以这也就是解释了为什么最后的bigDecimal5和6的结果不一样咯。
1.3、例子:使用compareTo()来比较BigDecimal
那如何解决呢?其实BigDecimal不仅提供了equals方法,还提供了一个compareTo()方法,这个方法其实就是只比较两个数值的大小,相等则返回0 ,看例子:
public static void main(String[] args) {
BigDecimal bigDecimal1 = new BigDecimal(1);
BigDecimal bigDecimal2 = new BigDecimal(1);
System.out.println(bigDecimal1.compareTo(bigDecimal2));
BigDecimal bigDecimal3 = new BigDecimal(1);
BigDecimal bigDecimal4 = new BigDecimal(1.0);
System.out.println(bigDecimal3.compareTo(bigDecimal4));
BigDecimal bigDecimal5 = new BigDecimal("1");
BigDecimal bigDecimal6 = new BigDecimal("1.0");
System.out.println(bigDecimal5.compareTo(bigDecimal6));
}
//输出结果:
0
0
0
问题二:BigDecimal使用double初始化时存在精度风险
BigDecimal使用double初始化时存在精度风险,那这是怎么一回事呢?其实在阿里开发手册中也有这么一条建议,或者说是要求吧
禁止使用构造方法BigDecimal(double)的方式把double值转化成BigDecimal对象。
我们知道,计算机是只认识二进制的,只认识0和1,也就是说任何数据都会转化成0和1存储在计算机中,整数简单,除二取余,逆序排列即可。而小数则不一定全部能转化成二进制,比如0.1,在转换的过程中会出现循环的情况,所以这种事无法正确的存储完整的数据的,计算机是无法精确的存储这种数据的,所以计算机采用的是一定的精度来解决这个问题的,这就是IEEE 754(IEEE二进制浮点数算术标准)规范的主要思想。
IEEE 754规定了多种表示浮点数值的方式,其中最常用的就是32位单精度浮点数和64位双精度浮点数。
在Java中,使用float和double分别用来表示单精度浮点数和双精度浮点数。
所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。
2.1、BigDecimal如何精确计数?
如果大家看过BigDecimal的源码,其实可以发现,实际上一个BigDecimal是通过一个"无标度值"和一个"标度"来表示一个数的。
在BigDecimal中,标度(精度)是通过scale字段来表示的。
而无标度值的表示比较复杂。当unscaled value超过阈值 (默认为Long.MAX_VALUE) 时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空。
Long.MAX_VALUE = 0x8000000000000000L;
Long.MIN_VALUE = 0x7fffffffffffffffL;
涉及到的字段就是这几个:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
/**
* The unscaled value of this BigDecimal, as returned by {@link
* #unscaledValue}.
*
* @serial
* @see #unscaledValue
*/
private final BigInteger intVal;
/**
* The scale of this BigDecimal, as returned by {@link #scale}.
*
* @serial
* @see #scale
*/
private final int scale; // Note: this may have any value, so
// calculations must be done in longs
/**
* If the absolute value of the significand of this BigDecimal is
* less than or equal to {@code Long.MAX_VALUE}, the value can be
* compactly stored in this field and used in computations.
*/
private final transient long intCompact;
}
BigDecimal主要是通过一个无标度值和标度来表示的。
那么标度到底是什么呢?除了scale这个字段,在BigDecimal中还提供了scale()方法,用来返回这个BigDecimal的标度。那么,scale到底表示的是什么,其实代码中的注释已经说的很清楚了:
- 如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。
如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。
而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。
我们都知道,想要创建一个对象,需要使用该类的构造方法,在BigDecimal中一共有以下4个构造方法:
其中 BigDecimal(int) 和 BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0。而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。
下面来看一下BigDecimal(double)有什么问题
2.2、BigDecimal(double)有什么问题
BigDecimal中虽然提供了一个通过double创建BigDecimal的方法,但是这其中也挖下了一个坑
我们知道,double表示的小数是不精确的,比如0.1这个数值,double只能表示他的近似值,所以当我们使用new BigDecimal(0.1)的时候,实际上创建出来的数值并不是正好等于0.1的,而是一个近似值。就如阿里开发手册中解释的那样。
所以,如果我们在代码中,使用BigDecimal(double) 来创建一个BigDecimal的话,那么是损失了精度的,这是极其严重的。
那么,该如何创建一个精确的BigDecimal来表示小数呢,答案是使用String创建。
而对于BigDecimal(String) ,当我们使用new BigDecimal(“0.1”)创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。
那么他的标度也就是1。
但是需要注意的是,new BigDecimal(“0.10000”)和new BigDecimal(“0.1”)这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,可以使用compareTo方法进行比较
那么,想要创建一个能精确的表示0.1的BigDecimal,请使用以下两种方式:
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
最后
以上就是贪玩荷花为你收集整理的用了BigDecimal后,计算结果一定精确?(关于BigDecimal的几个总结)的全部内容,希望文章能够帮你解决用了BigDecimal后,计算结果一定精确?(关于BigDecimal的几个总结)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复