目录
一、使用
在Java程序设计中,浮点数处理存在失真(丢失精度)问题,例如:
out.println(0.1 + 0.2); //输出0.30000000000000004
out.println(0.1 * 0.2); //输出0.020000000000000004
double num1 = 2.8f;
out.println(num1); //输出2.799999952316284
double a = 0.7;
out.println(new BigDecimal(a)); //输出0.6999999999999999555910790149937383830547332763671875
因此,在处理精确度和准确度要求都非常高的数据时,不宜直接使用单精度和双精度数据类型,通常应用BigDecimal(String val)来声明存储浮点数的变量,并用BigDecimal中对应的方法来计算,最后可通过其BigDecimal中的toString()方法获得对应的值
二、解析
为什么在对浮点数进行存储、计算、类型转换时会出现失真呢?其核心是因小数转二进制和浮点数的存储机制引起的。为什么用BigDecimal(String val)不会出现失真现象呢?这与BigDecimal的处理机制有关系。
1、浮点数存储机制
计算机先将数值转换成二进制并用科学计数法表示,然后根据类型存储0、1代码。
单精度浮点数存储格式:
1bit | 8bits | 23bits |
双精度浮点数存储格式:
1bit | 11bits | 52bits |
在上面两个表格中,第一列是符号位,第二列是存储指数的位数,第三列是存储尾数的位数(要减1)。因此,当单精度浮点数的尾数超过24位时,后面的二进制位数将丢失;双精度浮点数的尾数超过53位时,后面的二进制位数将丢失。
注意:要想具体了解浮点数的存储机制,请参考网上的其他资料,在此只说明造成失真的原因,因此不赘述。
2、小数转二进制的问题
小数转二进制是用该数乘以2,并取乘积的整数部分,直到乘积为0。
如:0.1转换成为二进制位0.0001100110011...,0.2转换成二进制为0. 001100110011...
以上两数转换成二进制时都出现了无限循环,因此,表示成科学计数法时,其尾数会超出浮点数存储要求的尾数,超出部分将会丢失,因此产生失真
3、BigDecimal的核心机制
用intCompact(声明形式:private final transient long intCompact)变量存储浮点数的有效数值,用scale(声明形式:private final int scale)变量存储浮点数的小数位数。如:
555.6666:intCompact变量会存入5556666,scale变量会存入4。
计算时,用对应的intCompact值参与运算,即存储和运算都使用了long型,所以不会失真。
要想得到真实的数字,只需调用toString方法即可,此方法会以字符串处理的方式,在intCompact表示的数值中加入小数点并返回
特别注意:为了不失真,尽量不使用public BigDecimal(double val,...)构造方法