Java浮点数使用小结
1.??? 引子
??? 平常在代码中,从不缺少使用浮点数的地方。浮点数可以使用float和double类型进行定义。默认都是使用的double类型,如果需要声明为float类型,需要显示地加F或者f,比如Float fNumber = 1.234F。往常个人在使用的时候,使用float居多(在java的基本类型中,Float是32位,4个字节;Double是64位,8个字节;所以float相对来说节省内存),近来踩了两个坑,觉得还是double比较省心。记录如下。
?
2.??? 问题1 精度错误2.1 表现形式问题一示例如下。??? 需要将一组对象转换成json,使用的json包是json-lib 2.2版本。
图2.1 json-lib包引用
?
图2.2 问题1示例
?图2.3 问题1结果
??? 可以看到在将浮点数转换成json数据的时候,Float类型的浮点数会出现精度错误的问题。
?
2.2 问题原因??? 排查原因,发现在json-lib的库里面,对于Float类型的数据,是先将其转换成Double类型之后,然后再进行处理的。处理代码如下。
?
图2.4 json-lib对于Float的处理
??? Float的doubleValue方法是直接进行类型转换,即return (double)value。
??? Float和Double在内存中的存储长度不同,将Float转换成Double类型的时候,如果直接取Float的doubleVlaue方法,会进行位数的补全;特别是在有小数部分的时候,这样是不能保证精度的,这样就引起了我们现在的问题。
2.3 解决方法
?
??? 解决问题的方法有两个思路。
在整个系统中以double类型存储,替换掉现有的以float类型存储的方式;找到float类型转换成double类型的安全的方法。??? 第一个思路,可以彻底的解决这个问题,但是对于一个系统来说,进行全局替换需要慎重,所以暂时不进行这样的修复;
??? 第二个思路,可以有以下几种方案。
??? 解决问题的代码如下。
?
图2.5 问题解决
??? 先判断要处理的值是float类型,然后调用Double的valueOf方法进行处理。
3.??? 问题2 大数表示问题3.1 表现形式
??? 解决了上面这个json输出的问题之后,又发现了一个问题。对于一些较大的数值,比如9999999.99;最终的输出结果为1.0E7,而不是预期的数值。
3.2 原因分析
??? 按理来说,float存储的范围应该远大于这个数值,经过一系列的调试,发现是float类型转换成String的时候出现的问题。进一步深入,发现是float类型的存储结构导致的。
??? 在java中,float类型存储的长度是32bit,double类型的存储长度是64bit。如下表所示。
表3.1 float和double在java中的存储
??? 其中:
??? 对于float来说,2^23=8388608;所以float类型的数值,如果有效数字的数值超过了8388608这个值,系统会自动找一个近似的结果进行代替。这也是我们这里存储9999999.99的时候,结果异常的原因。这个值在存储的时候就已经被处理成了1.0E7,所以不论采用什么方式进行处理,都不会有改善的。这里也说明,float类型,可以保证6-7位的有效数字(6位及以下是绝对可以保证的,但是7位的时候,这个有效数字的数值如果超过了8388608,就无法进行表示了)。
??? 对于double类型,2^52=4503599627370496;所以double类型的有效数字在15-16位。
??? 所以我们遇到的问题2,目前是没有办法进行解决的。后续还是安排使用double类型替换float类型。
?
4.??? 小结? ?? 本文从两个问题说明了float类型的“坑爹”之处,如果不是对业务以及业务的发展特别有把握,还是建议使用double类型。