一般出现丢失精度的场景:
- 整数值超出了整数类型的最大范围,就会丢失精度,所以java提供了java.math.BigInteger类来解决这个问题。
- 把一个数据范围大的类型的数值或者变量赋值给另一个数据范围较小的类型的变量,也可能会丢失精度。
- 直接使用浮点数进行小数计算,也不能准确计算,所以java提供了java.math.BigDecimal类来解决这个问题。
描述:商业计算往往要求结果精确,都是采用BigDecimal而不是浮点数Double或者Float来进行计算。
目录
使用BigDecimal需要注意除法divide的使用必须设置指定小数位和舍入模式。
为什么浮点数不能进行小数的精确运算?
float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。所以它们没有提供完全精确的结果,所以也就没办法用于要求精确结果的场合。具体底层为什么还未进行深入研究。
浮点数不能进行精确计算的举例:
/*--测试浮点数类型能否进行小数的精确计算--*/
@Test
public void testDouble() {
System.out.println(2.3 - 1.3);// 0.9999999999999998
System.out.println(1.4 - 1.3);// 0.09999999999999987
System.out.println(62556.0*0.12);//7506.719999999999
// 上面表明双精度浮点数不能进行精确的小数计算
System.out.println(2.3f - 1.3f);// 1.0
System.out.println(1.4f - 1.3f);// 0.100000024
// 上面表明单精度浮点数也不能进行精确的小数计算
}
BigDecimal的错误用法举例:
/*测试BigDecimal能否进行小数的精确计算*/
@Test
public void testBigDectimal1(){
BigDecimal loanAmount= new BigDecimal(62556.50);//用信贷款金额
BigDecimal loanRate=new BigDecimal(0.12);//执行年利率
//计算得到利息
BigDecimal interest = loanAmount.multiply(loanRate);
System.out.println(interest);//7506.779999999999722193333440145579515956342220306396484375
int compareTo = interest.compareTo(new BigDecimal(7506.78));
System.out.println(compareTo);//-1
}
BigDecimal正确用法举例:
/*测试BigDecimal能否进行小数的精确计算*/
@Test
public void testBigDectimal2(){
BigDecimal loanAmount= new BigDecimal("62556.50");//用信贷款金额
BigDecimal loanRate=new BigDecimal("0.12");//执行年利率
//计算得到利息
BigDecimal interest = loanAmount.multiply(loanRate);
System.out.println(interest);//7506.7800
int compareTo = interest.compareTo(new BigDecimal(7506.78));
System.out.println(compareTo);//1
}
/*测试BigDecimal能否进行小数的精确计算*/
@Test
public void testBigDectimal3(){
BigDecimal loanAmount= BigDecimal.valueOf(62556.50);//用信贷款金额
BigDecimal loanRate=BigDecimal.valueOf(0.12);//执行年利率
//计算得到利息
BigDecimal interest = loanAmount.multiply(loanRate);
System.out.println(interest);//7506.780
int compareTo = interest.compareTo(new BigDecimal(7506.78));
System.out.println(compareTo);//1
}
所以即便使用BigDecimal,要想不失精,有下面这个注意项:
使用参数为String的构造函数BigDecimal(String val),或者使用BigDecimal.valueOf(Double val)函数来实例化BigDecimal对象。
其实 ,看源码可知,BigDecimal.valueOf(Double val)函数底层就是构造函数BigDecimal(String val)。
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
使用BigDecimal需要注意除法divide的使用必须设置指定小数位和舍入模式。
在使用divide方法进行除法时当不整除时,出现无限循环小数时,就会抛异常。所以在使用divide方法时必须设置精确的小数点保留位和舍入模式,要养成这种代码习惯。
/*测试BigDecimal能否进行小数的精确计算,这里1/0.3是除不尽的*/
@Test
public void testBigDectimal4(){
BigDecimal bm1= BigDecimal.valueOf(1);
BigDecimal bm2=BigDecimal.valueOf(0.3);
BigDecimal bm3=bm1.divide(bm2,4,BigDecimal.ROUND_HALF_UP);
System.out.println(bm3);//3.3333
BigDecimal bm4 = bm1.divide(bm2);//抛异常no exact repreentable decimal result——没有精确的可表示的十进制结果
System.out.println(bm4);
}
使用BigDecimal需要注意比较方法的选择。
BigDecimal的比较(equals与compareTo)到底使用什么呢:
@Test
public void testCompareAndEquals(){
BigDecimal bm0 = new BigDecimal("-2.0");
BigDecimal bm1 = new BigDecimal("-2.00");
System.out.println(bm0.equals(bm1));//false 结果有false和ture
System.out.println(bm0.compareTo(bm1));//0 结果 有-1,0或1,因为这个 BigDecimal在数字上小于,等于或大于 val 。
/*--下面是翻阅JDK文档得到:--*/
//BigDecimal的compareTo方法:两个BigDecimal对象的价值相等但具有不同的比例(如2.0和2.00)被认为是相等的这种方法。
//BigDecimal的equals方法: 与compareTo不同,该方法认为两个BigDecimal对象只有在值和比例相等时才相等(因此,当通过此方法比较时,2.0不等于2.00)。
}
结论:对于BigDecimal的大小比较,使用equals方法的话不仅会比较值的大小,还会比较两个对象的精确度,而compareTo方法数值相同精度不同也会被视认为相等。所以当业务需求只要求比较值大小的时候就使用compare方法,而不是使用equals方法
BigDecimal定义的舍入模式的介绍:
这8种舍入模式在BigDecimal里都是静态常量,使用时直接类名调用即可。
public final static int ROUND_UP = 0;
public final static int ROUND_DOWN = 1;
public final static int ROUND_CEILING = 2;
public final static int ROUND_FLOOR = 3;
public final static int ROUND_HALF_UP = 4;
public final static int ROUND_HALF_DOWN = 5;
public final static int ROUND_HALF_EVEN = 6;
public final static int ROUND_UNNECESSARY = 7;
@Test
public void testSchema(){
/*--对于舍入模式ROUND_UP,可以理解为:指定小数位直接进位--*/
BigDecimal bm0 = new BigDecimal("2.32746").setScale(2, BigDecimal.ROUND_UP);//小数保留位为2,舍入模式为ROUND_UP
System.out.println(bm0);//2.33
BigDecimal bm0b = new BigDecimal("2.3212").setScale(2, BigDecimal.ROUND_UP);//小数保留位为2
System.out.println(bm0b);//2.33
/*--对于舍入模式ROUND_DOWN,可以理解为:直接截断--*/
BigDecimal bm1 = new BigDecimal("2.32746").setScale(2, BigDecimal.ROUND_DOWN);
System.out.println(bm1);//2.32
/*--对于舍入模式ROUND_CEILING,可以理解为:正数使用ROUND_UP规则,负数使用ROUND_DOWN规则--*/
BigDecimal bm2 = new BigDecimal("2.32111").setScale(2, BigDecimal.ROUND_CEILING );
System.out.println(bm2);//2.33
BigDecimal bm2b = new BigDecimal("-2.32111").setScale(2, BigDecimal.ROUND_CEILING );
System.out.println(bm2b);//-2.32
/*--对于舍入模式ROUND_FLOOR,可以理解为:正数使用截断规则即ROUND_DOWN,负数使用指定小数位进位的规则即ROUND_UP--*/
BigDecimal bm3 = new BigDecimal("2.32888").setScale(2, BigDecimal.ROUND_FLOOR );
System.out.println(bm3);//2.32
BigDecimal bm3b = new BigDecimal("-2.32111").setScale(2, BigDecimal.ROUND_FLOOR );
System.out.println(bm3b);//-2.33
/*--对于舍入模式ROUND_HALF_UP和ROUND_HALF_DOWN ,可以理解为:小学学的四舍五入,注:四舍五入是一种精确度的计数保留法,与数的正负无关哦--*/
BigDecimal bm4 = new BigDecimal("2.32888").setScale(2, BigDecimal.ROUND_HALF_UP );
System.out.println(bm4);//2.33
BigDecimal bm4b = new BigDecimal("-2.32888").setScale(2, BigDecimal.ROUND_HALF_UP );
System.out.println(bm4b);//-2.33
BigDecimal bm5 = new BigDecimal("2.32888").setScale(2, BigDecimal.ROUND_HALF_DOWN );
System.out.println(bm5);//2.33
BigDecimal bm5b = new BigDecimal("-2.32888").setScale(2, BigDecimal.ROUND_HALF_DOWN );
System.out.println(bm5b);//-2.33
/*--对于舍入模式ROUND_HALF_EVEN,可以理解为:银行家舍入模式:四舍六入五考虑,五后非空就进一,五后为空看奇偶,五前为偶应舍去,五前为奇要进一
* 它是IEEE754标准的推荐舍入标准。这一方式跟通常的四舍五入相比,平均数方面更能保持原有数据的特性。参见百度百科--*/
System.out.println("---------------");
BigDecimal bm6 = new BigDecimal("9.8249").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6);//9.82
BigDecimal bm6b = new BigDecimal("9.82671").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6b);//9.83
BigDecimal bm6c = new BigDecimal("9.8350").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6c);//9.84
BigDecimal bm6d = new BigDecimal("9.8351").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6d);//9.84
BigDecimal bm6f = new BigDecimal("9.8250").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6f);//9.82
BigDecimal bm6j = new BigDecimal("9.82501").setScale(2, BigDecimal.ROUND_HALF_EVEN );
System.out.println(bm6j);//9.83
/*--对于舍入模式ROUND_UNNECESSARY,本人目前还不太了解它的用法--*/
}
参考博客:BigDecimal使用总结以及使用时可能遇到的。