Java——double类型精度运算

写在前面——接近年底,项目遇到很多报表、结算等类型的需求,自然就会遇到很多double类型的逻辑运算,正巧之前没有总结过这方面的东西,所以就有了这篇帖子。

最开始做的时候比较渣,没有注意两个double类型做逻辑运算最后得到的数字是有误差的(原谅我这个时候有些粗心了),如下所示:

public static void main(String[] args) {
	  double a=1.47;
	  double b=2.34;
    System.out.println(a+b);//结果为:3.8099999999999996
  }

因为在实际项目中还有3位小数、4位小数等,都会出现误差,所以在很多语言中double类型的数值都不能直接进行运算,会出现这种精度问题,试想一下某个客户只有3.8099999999999996元,但是系统认为他有3.81元,可以购买3.81元的商品,久而久之那商家就亏大了,细思极恐啊有木有!

所以要进行double类型的逻辑运算的时候要怎么做才能保证不会出现精度的问题呢?我突然想到在之前看的一本非常经典的书叫做《Effective Java》,Java 之父 James Gosling非常推崇的一本书。里面就提到了float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。BigDecimal一共有4个构造方法,其中两个对我们有用的分别是:

  1、BigDecimal(double   val)     
                      Translates   a   double   into   a   BigDecimal.     
  2、BigDecimal(String   val)     
                      Translates   the   String   repre   sentation   of   a   BigDecimal   into   a   BigDecimal.

在看到这个api的时候第一反应就是直接传入double类型来进行构造BigDecimal就可以了啊,是不是感觉很简单?!那么我们来看看是不是就是这么简单。代码示例如下:

public static void main(String[] args) {
	    //定义两个double类型的数值
      double a=1.47;
      double b=2.34;
      //使用BigDecimal(double v)构造方法生成BigDecimal
      BigDecimal num1 = new BigDecimal(a);
      BigDecimal num2 = new BigDecimal(b);
      //使用BigDecimal的add方法进行运算,完成运算转回double类型
      double v = num1.add(num2).doubleValue();
      System.out.println(v);//输出结果为:3.8099999999999996
  }

what?!这样写,最终结果和两个double类型直接运算没有差别!?Java之父骗了我?!

这个时候打开BigDecimal 的API,我们再来看一下BigDecimal(double   val) 构造方法的详细说明会发现有这样一段话:Note:   the   results   of   this   constructor   can   be   somewhat   unpredictable.   One   might   assume   that   new   BigDecimal(.1)   is   exactly   equal   to   .1,   but   it   is   actually   equal   to   .1000000000000000055511151231257827021181583404541015625.   This   is   so   because   .1   cannot   be   represented   exactly   as   a   double   (or,   for   that   matter,   as   a   binary   fraction   of   any   finite   length).   Thus,   the   long   value   that   is   being   passed   in   to   the   constructor   is   not   exactly   equal   to   .1,   appearances   nonwithstanding.     
  The   (String)   constructor,   on   the   other   hand,   is   perfectly   predictable:   new   BigDecimal(".1")   is   exactly   equal   to   .1,   as   one   would   expect.   Therefore,   it   is   generally   recommended   that   the   (String)   constructor   be   used   in   preference   to   this   one.

英语不太好,翻译过来就是:注意:这个构造函数的结果可能有些不可预测。可以假定新的bigdecimal(.1)正好等于.1,但实际上等于.10000000000000055511151231257827021181583404541015625。这是因为.1不能精确地表示为一个双精度数(或者,就这点而言,不能精确地表示为任何有限长度的二分之一)。因此,传递给构造函数的长值不完全等于.1,外观是非独立的。

另一方面,(字符串)构造函数是完全可预测的:新的bigdecimal(“.1”)与预期的完全相同。因此,通常建议优先使用(字符串)构造函数。

so,这下就明白了,我们应该使用String类型的值来构造BigDecimal !!就是应该使用BigDecimal(String   val) 构造方法而不是使用BigDecimal(double   val)!!天啦,书中有时也没有颜如玉啊~~~~~~~     搞明白问题之后接下来上代码咯!

public static void main(String[] args) {
	   //定义两个double类型的数值
	   double a=1.47;
	   double b=2.34;
	   //把double类型的数值转换为String类型
       String valuea = String.valueOf(a);
       String valueb = String.valueOf(b);
       //传入处理好的String类型构造BigDecimal
       BigDecimal num1 = new BigDecimal(valuea);
       BigDecimal num2 = new BigDecimal(valueb);
       //使用BigDecimal的方法进行运算然后转回double类型
       System.out.println(num1.add(num2).doubleValue());//输出结果为3.81
  }

到这里,double类型运算精度丢失的问题就解决了,那么BigDecimal这个加减乘除都是对应哪些方法呢?下面我会一一列出常用的BigDecimal运算方法。

加法示例:

    //传入需要做加法操作的两个double数值
    public static double add(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }
    

减法示例:

    /**
     * 保证精度的减法运算。
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    } 
   

乘法运算:

    /**
     * 保证精度的乘法运算。
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static double mul(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

除法运算(除法因为可能除不尽会有些特殊处理):


    /**
     * 获取相对精度的除法运算。
     * 当发生除不尽的情况时,根据参数accuracy指定精度,之后的数四舍五入
     * @param v1 被除数
     * @param v2 除数
     * @param accuracy 表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1,double v2,int accuracy){
        //精度(accuracy)必须是正整数或零
        if(accuracy<0){
            throw new IllegalArgumentException(
                "The accuracy must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,accuracy,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

文章到这里就结束了,欢迎被Java深深吸引并对Java有浓厚兴趣的业内小伙伴一起交流心得,一起进步!

————————————纯属原创,不喜勿喷。 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值