谨以此篇献给辛勤工作(moyu)的1024。

java带小数点的数据存什么类型 java处理小数_java带小数点的数据存什么类型

0.1024问题

又到了,一年一度的1024程序员佳节,众所周知1024的二进制是,等一下让程序跑一会…

System.out.println(new BigInteger("1024", 10).toString(2));

对,是10000000000。那么0.1024的二进制是多少呢?

System.out.println(1024 - 1);
System.out.println(0.1024 -0.1);
System.out.println(0.9 - 0.8);

以上三个输出结果是多少呢?

1023 当然是1023啦,小傻瓜
0.0023999999999999994
0.09999999999999998

好了,老程序员请不要疑惑,新入坑的1024们请张大你的嘴巴。

java带小数点的数据存什么类型 java处理小数_jvm_02

原因

运算过程中计算机会用浮点数表示小数,导致精度丢失出现以上问题。

浮点数是计算机用来表示小数的一种数据类型,采用科学计数法。

在Java中,double是双精度,64位,浮点数,默认是0.0d。float是单精度,32位,浮点数,默认是0.0f;

32位单精度二进制 = [1个符号位] [8个阶码位] [23个尾数位]
64位单精度二进制 = [1个符号位] [11个阶码位] [52个尾数位]
小数 = [正负符号位]  [整数部分] . [小数部分]

小数转换成二进制

十进制0.9 = 二进制1100 1100 1100 1100 1100 1100 其中1100循环
  计算过程:  0.9 x 2 = 1.8取整得1 取上次结果的小数部分乘以2
                  0.8 x 2 = 1.6取整得1  取1.6的小数部分即0.6乘以2

                  0.6 x 2 = 1.2取整得1  取1.2的小数部分即0.2乘以2

                  0.2 x 2 = 0.4取整得0  取0.4的小数部分0.4乘以2

                  0.4 x 2 = 0.8取整得0  取上次结果的小数部分乘以2

                  0.8 x 2 = 1.6取整得1

                  0.6 x 2 = 1.2取整得1

                           ...循环

当到达一定值自动开始使用科学计数法,并保留相关精度的有效数字,所以结果是个近似数,并且指数为整数。在十进制中小数有些是无法完整用二进制表示的。所以只能用有限位来表示,从而在存储时可能就会有误差。

java带小数点的数据存什么类型 java处理小数_java_03

解决

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。

float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。
BigDecimal所创建的是对象,必须调用其相对应的方法才能进行数学运算。方法中的参数也必须是BigDecimal的对象。

示例:

BigDecimal bigDecimal1 = new BigDecimal("0.1024");
BigDecimal bigDecimal2 = new BigDecimal("0.1");
//0.1024 - 0.1
System.out.println(bigDecimal1.subtract(bigDecimal2));
//保留小数X位
System.out.println(bigDecimal1.setScale(2, BigDecimal.ROUND_DOWN));

结果:

0.0024
0.10

注:BigDecimal(double) 不推荐使用

BigDecimal bigDecimal1 = new BigDecimal(0.1024);
BigDecimal bigDecimal2 = new BigDecimal(0.1);
//0.1024 - 0.1
System.out.println(bigDecimal1.subtract(bigDecimal2));

结果:

0.00239999999999999935607064571740920655429363250732421875

java带小数点的数据存什么类型 java处理小数_java_04


源码中也提到此构造函数的结果可能有些不可预测。可以使用String构造函数是完全可预测的。

舍入模式

ROUND_UP       		//向远离0的方向舍入
ROUND_DOWN     		//向零方向舍入
ROUND_CEILING  		//向正无穷方向舍入
ROUND_FLOOR    		//向负无穷方向舍入
ROUND_HALF_UP    	//四舍五入
ROUND_HALF_DOWN    //向最近的一边舍入,如果两边(的距离)是相等,向下舍入, 例如1.85 保留一位小数结果为1.8
ROUND_HALF_EVEN    //向最近的一边舍入,如果两边是相等,保留位数是奇数,使用ROUND_HALF_UP,偶数,使用ROUND_HALF_DOWN
ROUND_UNNECESSARY  //计算结果是精确的,不需要舍入模式