浮点数(float,double)表数范围和精度问题
其实之前就遇到过浮点数精度丢失的问题,但是一直没有去深入研究,只是停留在知识记忆的层面,久而久之发现之前的东西都忘记了,之所以想要围绕这个问题来写一篇文章,是因为最近出现的一个bug,在项目中一个列表显示中id列后台数据是long类型,前端用easyui显示的时候发现数据显示错乱,显示的数据并不是数据库拿到的数据,之前是没有这个问题的,数据量多了以后就出现了这个问题,后来经过各种排查,苦心孤诣探索原因,然并卵,这时候偶然想到会不会是long类型长度超出的问题,于是把后台long类型字段转为String后再返回给前段,显示正常了。原来js中整数只能表示15位,多于15位就会出现精度问题;
JavaScript不是类型语言,与其他雨多编程语言不同,js没有区分定义short,int,long这些类型,
只有Number一种数字类型,Number本质是浮点数,跟java中的double类似,64位,8字节的长度
简单定义
在IEEE754标准中进行了单精度浮点数(float)和双精度数浮点数(double)的定义。
float有32bit,double有64bit。它们的构成包括符号位、指数位和尾数位。结构组成
类型 符号位 指数位 尾数位 floa(32bit) 最左侧(第31位) 第30-23位(占8bit) 第22-0位(占23bit) double(64bit) 最左侧(第63位) 第62-52位(占11bit) 第51-0位(占52bit) 取值范围
取值范围主要看指数部分:
float的指数部分有8bit(2^8),由于是有符号型,所以得到对应的指数范围-128~128。取值范围为:
-2^128到2^128,约等于-3.4E38 ~ +3.4E38 ;
double的指数部分有11bit(2^11) , 对应的指数范围-1024~1024。 取值范围为:-2^1024~2^1024,约 等于-1.797E308 ~ +1.797E308;精度
精度(有效数字)主要看尾数位:
float的尾数位是23bit,对应7~8位十进制数,所以有效数字有的编译器是7位,也有的是8位;
double的尾数位是52bit,对应15~16位十进制数,有效数字位15位或16位;java中浮点数运算精度丢失问题
项目中用浮点数运算时通常会出现精度丢失的问题,比如:public class Test { public static void main(String[] args) { double a = 0.2; double b = 0.4; System.out.println(a); System.out.println(b); System.out.println(a+b); System.out.println(a*b); } }
输出结果为:
0.2
0.4
0.6000000000000001
0.08000000000000002可以看到0.2+0.4并不等于0.6, 0.2*0.4也不等于0.08,这就是浮点运算中出现的精度丢失问题,他跟我们通常工程运算中以为的不同,这是由于计算机二进制存储的原因造成的,那么怎么解决呢?在项目中通常会避免使用double类型,而是使用BigDecimal类来进行浮点数的运算。
BigDecimal public BigDecimal(double val) 将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。
注:
此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1,但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 double 无法准确地表示为 0.1(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
另一方面,String 构造方法是完全可预知的:写入 new BigDecimal(“0.1”) 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。
当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。
解决方法
在需要精确的表示两位小数时我们需要把他们转换为BigDecimal对象,然后再进行运算。
使用BigDecimal(double val)构造函数时仍会存在精度丢失问题,通常使用BigDecimal(String val)
这就需要先把double转换为字符串然后在作为BigDecimal(String val)构造函数的参数。转换为BigDecimal对象
之后再进行加减乘除操作,这样精度就不会出现问题了。所以有关金钱数据存储都使用BigDecimal。
import java.math.BigDecimal;
public class Test {
public static void main(String[] args) {
double a = 0.2;
double b = 0.4;
BigDecimal c = BigDecimal.valueOf(a);
BigDecimal d = BigDecimal.valueOf(b);
System.out.println(c.add(d));
System.out.println(c.multiply(d));
}
}
输出:
0.6
0.08