Java编程经验---浮点型数值问题
前言
最近看了阿里的Java编程手册确实启发很多,有必要拾人牙慧补营养了。关于数值这个问题,确实是超出我自己的认知之外了,所以有意去仔细了解Java对于数值的处理,找出那些匪夷所思的坑。简单的来说当前的计算机是二进制的世界,而我们是十进制的世界,在转换的过程中,出于对存储空间的考虑,转换过程可能会有精度数据的丢失。所以在这里详细的分析一下。
原理解释
浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。
回顾一下什么是科学计数法?(下面来自百度百科的小例子)
运用科学记数法a×10^n的数字,它的精确度以a的最后一个数在原数中的数位为准。如:
13600
,
精
确
到
十
位
,
记
作
:
1.360
∗
1
0
4
13600,精确到十位,记作:1.360*10^4
13600,精确到十位,记作:1.360∗104
13200 , 精 确 到 百 位 , 记 作 : 1.32 ∗ 1 0 4 13200 ,精确到百位,记作:1.32*10^4 13200,精确到百位,记作:1.32∗104
322000 , 精 确 到 千 位 , 记 作 : 3.22 ∗ 1 0 5 322000,精确到千位,记作:3.22*10^5 322000,精确到千位,记作:3.22∗105
接着来看双精度浮点型数值
符号位用来记录数值的正负。
举例
现在举一个例子(来源阿里 孤尽)
后端的long型 -> 前端double Number
前端:
3
=
2
1
∗
1.1
3 = 2^1 * 1.1
3=21∗1.1
5 = 2 2 ∗ 1.01 5 = 2^2 * 1.01 5=22∗1.01
9 = 2 3 ∗ 1.001 9 = 2^3 * 1.001 9=23∗1.001
( 2 53 + 1 ) = 2 53 ∗ 1.0000000...01 (2^{53}+1) = 2^{53}*1.0000000...01 (253+1)=253∗1.0000000...01
此时最后的1,会因为尾数(fraction)位置的限制而溢出。此时传到前端的数值精度将会被截断,此时数值会不准确。
且注意超过2^53 后,并非一定出错,类似于2^54这类不使用尾数的值,依然可以准确表示,所以会有随机产生Bug的可能。
解决办法
前后端传值的情况,如果数值精确度高,或数值巨大,推荐使用使用String进行传递。
再举一个例子(Java 手册)
后端浮点和浮点的计算与比较
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
if (a == b) {
// 预期进入此代码块,执行其它业务逻辑
// 但事实上a==b的结果为false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 预期进入此代码块,执行其它业务逻辑
// 但事实上equals的结果为false
}
解决办法
/* (1) 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。 float a = 1.0F - 0.9F; */
float b = 0.9F - 0.8F;
float diff = 1e-6F;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
/* (2) 使用BigDecimal来定义值,再进行浮点数的运算操作。*/
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.compareTo(y) == 0) {
System.out.println("true");
}
如上所示BigDecimal的等值比较应使用compareTo()方法,而不是equals()方法。 说明:equals()方法会比较值和精度(1.0与1.00返回结果为false),而compareTo()则会忽略精度。
结尾
点滴积累,才能扎实成长。
时间有限,如有问题,及时更正。