java中double和float精度丢失问题

匿名 (未验证) 提交于 2019-12-02 21:53:52

为什么会出现这个问题呢,就这是java和其它计算机语言都会出现的问题,下面我们分析一下为什么会出现这个问题:
float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以我们不应该用于精确计算的场合。float和double类型尤其不适合用于货币运算,因为要让一个float或double精确的表示0.1或者10的任何其他负数次方值是不可能的(其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10)。

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float 和 double 作精确运算的时候要特别小心。

现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
首先我们要搞清楚下面两个问题:
(1) 十进制整数如何转化为二进制数
算法很简单。举个例子,11表示成二进制数:
11/2=51
5/2=21
2/2=10
1/2=01
011二进制表示为(从下往上):1011
这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。
(2) 十进制小数如何转化为二进制数
算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数
0.9*2=1.81
0.8(1.8的小数部分)*2=1.61
0.6*2=1.21
0.2*2=0.40
0.4*2=0.80
0.8*2=1.61
0.6*2=1.20
0.91100100100100......
注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。

解决方法

使用BigDecmal,而且需要在构造参数使用String类型。

在《Effective Java》这本书中就给出了一个解决方法。该书中也指出,float和double只能用来做科学计算或者是工程计算,在商业计算等精确计算中,我们要用java.math.BigDecimal。

BigDecimal(double value) // 将double型数据转换成BigDecimal型数据

// BigDecimal(String value)能够将String型数据转换成BigDecimal型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
packagecom.util;
importjava.math.BigDecimal;
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精确的浮点数运算,包括加减乘除和四舍五入。
*/
publicclassArith {
//默认吃吃饭运算精度
privatestaticfinalint10;
//这个类不能实例化
privateArith() {
}
/**
* 提供精确的加法运算
*
* @param v1
* @param v2
* @return 两个参数的和
*/
publicstaticdoubleadd(doubledoublev2) {
newBigDecimal(Double.toString(v1));
newBigDecimal(Double.toString(v2));
returnb1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算
* @param v1
* @param v2
* @return两个参数的差
*/
publicstaticdoublesub(doubledoublev2) {
newBigDecimal(Double.toString(v1));
newBigDecimal(Double.toString(v2));
returnb1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1
* @param v2
* @return 两个参数的积
*/
publicstaticdoublemul(doubledoublev2) {
newBigDecimal(Double.toString(v1));
newBigDecimal(Double.toString(v2));
returnb1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除非运算,当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入
* @param v1
* @param v2
* @return 两个参数的商
*/
publicstaticdoublediv(doubledoublev2) {
returndiv(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入
* @param v1
* @param v2
* @param scale
* @return 两个参数的商
*/
publicstaticdoublediv(doubledoubleintscale) {
if0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
newBigDecimal(Double.toString(v1));
newBigDecimal(Double.toString(v2));
returnb1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* 提供精确的小数位四舍五入处理
*
* @param v
* @param scale
* @return 四舍五入后的结果
*/
publicstaticdoubleround(doubleintscale) {
if0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
newBigDecimal(Double.toString(v));
newBigDecimal("1");
returnb.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}

  

附上Arith的源代码,大家只要把它编译保存好,要进行浮点数计算的时候,在你的源程序中导入Arith类就可以使用以上静态方法来进行浮点数的精确计算了。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!