关于float和double丢失精度问题及解决⽅案
double result = 1.0 - 0.9;
System.out.println(result);//0.09999999999999998
出现这种结果的原因:float和double类型尤其不适合⽤于货币运算,因为要让⼀个float或double精确的表⽰0.1或者任何其他负数次⽅值是不可能的(⼗进制系统中不能准确的表⽰出1/3,同样⼆进制系统也不能准确的表⽰1/10)。
1.⼗进制整数转为⼆进制数:
例⼦:11表⽰成⼆进制数:
11/2 =5 余1
5/2 = 2 余1
2/2 = 1 余0
1/2 = 0 余1
0结束,11⼆进制表⽰为(从下往上):1011
注意:只要遇到除以后的结果为0就结束了。所有的整数除以2⼀定能够最终得到0,但是⼩数就不能,⼩数转变为⼆进制的算法就有可能会⽆限循环下去。
2.⼗进制⼩数转为⼆进制数
算法是乘以2知道没有了⼩数为⽌,例⼦:
0.9表⽰成⼆进制数:
0.9*2 = 1.8 取整数部分:1
0.8*2 = 1.6 取整数部分:1
0.6*2 = 1.2 取整数:1
0.2*2 = 0.4 取整数:0
0.4*2 = 0.8 取整数:0
0.8*2 = 1.6 取整数:1
。。。。
0.9⼆进制表⽰为(从上往下):
注意:上⾯的计算过程循环了,也就是说乘以2永远不能消灭⼩数部分,这样算法将⽆限下去。显然,⼩数的⼆进制表⽰有时是不能精确的。道理很简单,⼗进制系统中不能准确的表⽰出1/3,同样⼆进制也⽆法准确的表⽰1/10。这也是浮点型出现精度丢失问题的主要原因。
解决⽅案⼀:
如果不介意记录⼗进制的⼩数点,⽽且数值不⼤,那么可以使⽤long,int等基本类型,
int resultInt = 10 -9;
double result = (double)resultInt / 100;//⾃⼰控制⼩数点
解决⽅案⼆:
使⽤BigDecimal,⽽且需要在构造参数使⽤String类型.
float和double只能⽤来做科学计算或者⼯程计算,在商业计算等精确计算中,要⽤java.math.BigDeci
mal。
在《Effective 》这本书中就给出了⼀个解决⽅法。该书中也指出,float和double只能⽤来做科学计算或者是⼯程计算,在商业计算等精确计算中,我们要⽤java.math.BigDecimal。
BigDecimal类⼀个有4个⽅法,我们只关⼼对我们解决浮点型数据进⾏精确计算有⽤的⽅法,即
BigDecimal(double value) // 将double型数据转换成BigDecimal型数据
思路很简单,我们先通过BigDecimal(double value)⽅法,将double型数据转换成BigDecimal数据,然后就可以正常进⾏精确计算了。等计算完毕后,我们可以对结果做⼀些处理,⽐如对除不尽的结果可以进⾏四舍五⼊。最后,再把结果由BigDecimal型数据转换回double型数据。
这个思路很正确,但是如果你仔细看看API⾥关于BigDecimal的详细说明,你就会知道,如果需要精确计算,我们不能直接⽤double,⽽⾮要⽤ String来构造BigDecimal不可!所以,我们⼜开始关⼼BigDecimal类的另⼀个⽅法,即能够帮助我们正确完成精确计算的 BigDecimal(String value)⽅法。
// BigDecimal(String value)能够将String型数据转换成BigDecimal型数据
那么问题来了,想像⼀下吧,如果我们要做⼀个浮点型数据的加法运算,需要先将两个浮点数转为Stri
ng型数据,然后⽤BigDecimal(String value)构造成BigDecimal,之后要在其中⼀个上调⽤add⽅法,传⼊另⼀个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。如果每次做浮点型数据的计算都要如此,你能够忍受这么烦琐的过程吗?⾄少我不能。所以最好的办法,就是写⼀个类,在类中完成这些繁琐的转换过程。这样,在我们需要进⾏浮点型数据计算的时候,只要调⽤这个类就可以了。⽹上已经有⾼⼿为我们提供了⼀个⼯具类Arith来完成这些转换操作。它提供以下静态⽅法,可以完成浮点型数据的加减乘除运算和对其结果进⾏四舍五⼊的操作:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
下⾯会附上Arith的源代码,⼤家只要把它编译保存好,要进⾏浮点数计算的时候,在你的源程序中导⼊Arith类就可以使⽤以上静态⽅法来进⾏浮点数的精确计算了。
01. import java.math.BigDecimal;
02.
03. /**
04. * 由于Java的简单类型不能够精确的对浮点数进⾏运算,这个⼯具类提供精
05. * 确的浮点数运算,包括加减乘除和四舍五⼊。
06. */
07.
08. public class Arith{
09. //默认除法运算精度
10. private static final int DEF_DIV_SCALE = 10;
11. //这个类不能实例化
12. private Arith(){
13. }
14.
15. /**
16. * 提供精确的加法运算。
17. * @param v1 被加数
18. * @param v2 加数
19. * @return 两个参数的和
20. */
21. public static double add(double v1,double v2){
22. BigDecimal b1 = new String(v1));
23. BigDecimal b2 = new String(v2));
24. return b1.add(b2).doubleValue();
25. }
26. /**
27. * 提供精确的减法运算。
28. * @param v1 被减数
29. * @param v2 减数
30. * @return 两个参数的差
31. */
32. public static double sub(double v1,double v2){
33. BigDecimal b1 = new String(v1));
34. BigDecimal b2 = new String(v2));
35. return b1.subtract(b2).doubleValue();
36. }
37. /**
38. * 提供精确的乘法运算。
39. * @param v1 被乘数
40. * @param v2 乘数
41. * @return 两个参数的积
42. */
43. public static double mul(double v1,double v2){
44. BigDecimal b1 = new String(v1));
45. BigDecimal b2 = new String(v2));
46. return b1.multiply(b2).doubleValue();
47. }
48.
49. /**
50. * 提供(相对)精确的除法运算,当发⽣除不尽的情况时,精确到
51. * ⼩数点以后10位,以后的数字四舍五⼊。
52. * @param v1 被除数
53. * @param v2 除数
54. * @return 两个参数的商
55. */
56. public static double div(double v1,double v2){
57. return div(v1,v2,DEF_DIV_SCALE);
58. }
59.
60. /**
61. * 提供(相对)精确的除法运算。当发⽣除不尽的情况时,由scale参数指
62. * 定精度,以后的数字四舍五⼊。
63. * @param v1 被除数
64. * @param v2 除数
65. * @param scale 表⽰表⽰需要精确到⼩数点以后⼏位。
66. * @return 两个参数的商
67. */
68. public static double div(double v1,double v2,int scale){
69. if(scale<0){
70. throw new IllegalArgumentException(
71. "The scale must be a positive integer or zero");
bigdecimal除法保留小数72. }
73. BigDecimal b1 = new String(v1));
74. BigDecimal b2 = new String(v2));
75. return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
76. }
77.
78. /**
79. * 提供精确的⼩数位四舍五⼊处理。
80. * @param v 需要四舍五⼊的数字
81. * @param scale ⼩数点后保留⼏位
82. * @return 四舍五⼊后的结果
83. */
84. public static double round(double v,int scale){
85.
86. if(scale<0){
87. throw new IllegalArgumentException(
88. "The scale must be a positive integer or zero");
89. }
90. BigDecimal b = new String(v));
91. BigDecimal one = new BigDecimal("1");
92. return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
93. }
94. };
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论