关于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二进制表示为(从上往下):1100100100100.......

注意:上面的计算过程循环了,也就是说乘以2永远不能消灭小数部分,这样算法将无限下去。显然,小数的二进制表示有时是不能精确的。道理很简单,十进制系统中不能准确的表示出1/3,同样二进制也无法准确的表示1/10。这也是浮点型出现精度丢失问题的主要原因。


解决方案一:

如果不介意记录十进制的小数点,而且数值不大,那么可以使用long,int等基本类型,

int resultInt = 10 -9;

double result  = (double)resultInt / 100;//自己控制小数点


解决方案二:


使用BigDecimal,而且需要在构造参数使用String类型.

float和double只能用来做科学计算或者工程计算,在商业计算等精确计算中,要用java.math.BigDecimal。

在《Effective Java》这本书中就给出了一个解决方法。该书中也指出,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型数据

    那么问题来了,想像一下吧,如果我们要做一个浮点型数据的加法运算,需要先将两个浮点数转为String型数据,然后用 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类就可以使用以上静态方法来进行浮点数的精确计算了。

  1. import java.math.BigDecimal;  
  2.   
  3. /** 
  4. * 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精 
  5. * 确的浮点数运算,包括加减乘除和四舍五入。 
  6. */  
  7.   
  8. public class Arith{  
  9.     //默认除法运算精度  
  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 BigDecimal(Double.toString(v1));  
  23.         BigDecimal b2 = new BigDecimal(Double.toString(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 BigDecimal(Double.toString(v1));  
  34.         BigDecimal b2 = new BigDecimal(Double.toString(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 BigDecimal(Double.toString(v1));  
  45.         BigDecimal b2 = new BigDecimal(Double.toString(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");  
  72.         }  
  73.         BigDecimal b1 = new BigDecimal(Double.toString(v1));  
  74.         BigDecimal b2 = new BigDecimal(Double.toString(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 BigDecimal(Double.toString(v));  
  91.         BigDecimal one = new BigDecimal("1");  
  92.         return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();  
  93.     }  
  94. };  


  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值