我是靠谱客的博主 感动指甲油,这篇文章主要介绍double类型精度丢失问题以及解决方法double类型精度丢失问题:,现在分享给大家,希望可以做个参考。

double类型精度丢失问题:

(1)加法运算。

复制代码
1
2
3
4
5
6
7
public static void main(String[] args) { double number1 = 1; double number2 = 20.2; double number3 = 300.03; double result = number1 + number2 + number3; System.out.println("使用double运算结果: "+result); }

打印结果如下:

        使用double运算结果: 321.22999999999996。。。

 (2)减法运算。

复制代码
1
2
3
double d1 = 2.11; double d2 = 2.10; System.out.println( d1 - d2);

口算结果是0.01。可是用程序执行出来的结果却出乎意料,执行结果为0.009999999999999787。

(3)乘法运算。

复制代码
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) { double a = 152.70; System.out.println(a); System.out.println(a * 100);//15280.000000000002 System.out.println(Math.round(a * 100));//还凑合 double b = 152.70; System.out.println(b); System.out.println(b * 100);//15269.999999999998 System.out.println((int)(b * 100));//15269 错误!!!不是想要的 }

然后把15280.000000000002 - 15280 = 1.8189894035458565E-12这个结果返回给了前端, 

前端拿去和0比较, 结果就炸了

原因:就是double的精度问题, 单个double数可能就有损失精度,,多个double数运算(减法和乘法都输入加法)可能导致损失的精度更多。。。

为什么double类型会出现精度丢失问题?


        我们知道,计算机发展了如此长的一段时间,但它始终只能识别0和1(即二进制)。无论我们使用哪种编程语言,在哪种编译环境下工作,都要先把源代码翻译成二进制的机器码才能被计算机所识别。

        举个简单的例子,在源程序里面2.4,是十进制的,但计算机不能直接识别,要先编译成二进制。

        那么问题来了,2.4的二进制并非是精确的2.4,反而是最为接近的二进制表示是2.39999999996。

        原因在于浮点数由两部分组成:指数和尾数。如果知道怎么进行浮点数的二进制和十进制转换,应该不难理解。

        如果在这个转换过程中,浮点数参与了计算,那么在转换的过程中就会变得不可预知,并且变得不可逆。我们有理由相信,就是在这个过程中,发生了精度的丢失。

        而至于为什么有些浮点计算会得到准确的结果,应该也是碰巧那个计算二进制和十进制之间能够准确转换。

而当输出单个浮点型数据时,可以正确输出,如:

复制代码
1
2
Double num3 = 2.4; System.out.println(num3);

输出的结果是2.4,而不是2.39999999996。也就是说,不进行浮点计算的时候,在十进制里浮点数能正确显示。

        这正印证了上面的说法,即如果浮点数参与了计算,那么浮点数二进制与十进制的转换过程就会变得不可预知,并且变得不可逆。

        结论就是,在Java中,浮点数不精确,因为计算机内部无法用二进制的小数来精确的表达。

经典问题:浮点数精度丢失

精度丢失的问题是在其他计算机语言中也都会出现,float和double类型的数据在执行二进制浮点运算的时候,并没有提供完全精确的结果。

        产生误差不在于数的大小,而是因为数的精度。

关于浮点数存储精度丢失的问题,话题过于庞大,感兴趣的同学可以自行搜索一下:【解惑】剖析float型的内存存储和精度丢失问题

        这里简单讨论一下十进制数转二进制为什么会出现精度丢失的现象,十进制数分为整数部分和小数部分,我们分开来看看就知道原因为何:

十进制整数如何转化为二进制整数?
将被除数每次都除以2,只要除到商为0就可以停止这个过程。

5 / 2 = 2 余 1

2 / 2 = 1 余 0

1 / 2 = 0 余 1

// 结果为 101

这个算法永远都不会无限循环,整数永远都可以使用二进制数精确表示,但小数呢?

十进制小数如何转化为二进制数?
每次将小数部分乘2,取出整数部分,如果小数部分为0,就可以停止这个过程

0.1 * 2 = 0.2 取整数部分0

0.2 * 2 = 0.4 取整数部分0

0.4 * 2 = 0.8 取整数部分0

0.8 * 2 = 1.6 取整数部分1

0.6 * 2 = 1.2 取整数部分1

0.2 * 2 = 0.4 取整数部分0

//... 我想写到这就不必再写了,你应该也已经发现,上面的过程已经开始循环,小数部分永远不能为0

这个算法有一定概率会存在无限循环,即无法用有限长度的二进制数表示十进制的小数,这就是精度丢失问题产生的原因。

浮点数并不适合用于精确计算,而适合进行科学计算。

        float和double型用来表示带有小数点的数,那为什么我们不称他们为小数或实数,要叫浮点数呢?是因为这些数都以科学计数法的形式存储。

        当一个数如50.534,转换成科学计数法的形式为5.053e1,它 的小数点移动到了一个新的位置(即浮动了)。

        可见,浮点数本来就是用于科学计算的,用来进行精确计算实在太不合适了。

如何用BigDecimal解决double精度问题?


 我们已经明白为什么精度会存在丢失现象,那么我们就应该知道,当某个业务场景对double数据的精度要求非常高时,就必须采取某种手段来处理这个问题,

        这也是BigDecimal为什么会被广泛应用于金额支付场景中的原因啦。

        BigDecimal类位于java.math包下,用于对超过16位有效位的数进行精确的运算。

        一般来说,double类型的变量可以处理16位有效数,

        但实际应用中,如果超过16位,就需要BigDecimal类来操作。

new BigDecimal(double val)
        该方法是不可预测的,以0.1为例,你以为你传了一个double类型的0.1,最后会返回一个值为0.1的BigDecimal吗?不会的,原因在于,0.1无法用有限长度的二进制数表示,无法精确地表示为双精度数,最后的结果会是0.100000xxx。

new BigDecimal(String val)
        该方法是完全可预测的,也就是说你传入一个字符串"0.1",他就会给你返回一个值完全为0,1的BigDecimal,官方也表示,能用这个构造函数就用这个构造函数叭。

BigDecimal.valueOf(double val)
        第二种构造方式已经足够优秀,可你还是想传入一个double值,怎么办呢?官方其实提供给你思路并且实现了它,可以使用Double.toString(double val)先将double值转为String,再调用第二种构造方式,你可以直接使用静态方法:valueOf(double val)。

        总结:将double转为BigDecimal的时候,需要先把double转换为字符串,然后再作为BigDecimal(String val)构造函数的参数,这样才能避免出现精度问题。

​​​​​​​

以下是提供的double精确运算工具类(如果计算的数值总和很大很大,超过50 0000,请使用带有ToStr的方法,不然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
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package com.phone.common_library; import java.math.BigDecimal; public class BigDecimalManager { /** * double类型的加法运算(不需要舍入) * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double additionDouble(double m1, double m2) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.add(p2).doubleValue(); } /** * double类型的加法运算(需要舍入,保留三位小数) * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double additionDouble(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double类型的超大数值加法运算(需要舍入,保留三位小数) * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static String additionDoubleToStr(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double类型的减法运算 * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double subtractionDouble(double m1, double m2) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.subtract(p2).doubleValue(); } /** * double类型的减法运算(需要舍入,保留三位小数) * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double subtractionDouble(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double类型的超大数值减法运算(需要舍入,保留三位小数) * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static String subtractionDoubleToStr(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double类型的乘法运算 * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double multiplicationDouble(double m1, double m2) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.multiply(p2).doubleValue(); } /** * double类型的乘法运算 * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double multiplicationDouble(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double类型的超大数值的乘法运算 * @param m1 * @param m2 * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static String multiplicationDoubleToStr(double m1, double m2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double类型的除法运算 * @param m1 * @param m2 * @param scale * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static double divisionDouble(double m1, double m2, int scale) { if (scale < 0) { throw new IllegalArgumentException("Parameter error"); } BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double类型的超大数值的除法运算 * @param m1 * @param m2 * @param scale * @return 不加doubleValue()则, 返回BigDecimal对象 */ public static String divisionDoubleToStr(double m1, double m2, int scale) { if (scale < 0) { throw new IllegalArgumentException("Parameter error"); } BigDecimal p1 = new BigDecimal(Double.toString(m1)); BigDecimal p2 = new BigDecimal(Double.toString(m2)); return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } }

·测试、验证问题是否解决:

复制代码
1
2
3
4
5
public static void main(String[] args) { double result1 = additionDouble(1, 20.2); double result2 = additionDouble(result1, 300.03); System.out.println("使用BigDecimal运算结果:" + result2); }

结果如下: 

使用BigDecimal运算结果:321.23

完美解决了double类型数据加减操作时精度丢失的问题。 

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:RxJava2和Retrofit2项目,添加自动管理token功能,添加RxJava2生命周期管理,使用App架构设计是MVP模式和MVVM模式,同时使用组件化,部分代码使用Kotlin,此项目持续维护中。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

最后

以上就是感动指甲油最近收集整理的关于double类型精度丢失问题以及解决方法double类型精度丢失问题:的全部内容,更多相关double类型精度丢失问题以及解决方法double类型精度丢失问题内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(96)

评论列表共有 0 条评论

立即
投稿
返回
顶部