BigDecimal使用总结

      过去的工作开发中,有时候会使用到BigDecimal去定义一些变量,一直不了解具体的使用场景,这里学习记录一下。

一、使用背景

     借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

二、测试探索

     我们先看下面的测试demo:

import org.junit.Test;

import java.math.BigDecimal;


public class BigDecimalTest {

    @Test
    public void test1(){
        System.out.println(0.05+0.01);
        System.out.println(1.0-0.42);
        System.out.println(4.015*100);
        System.out.println(123.3/100);
    }

    @Test
    public void test2(){
        BigDecimal b1 = new BigDecimal(0.05);
        BigDecimal b2 = new BigDecimal(0.01);
        System.out.println(b1.add(b2));
    }

    @Test
    public void test3(){
        BigDecimal b1 = new BigDecimal("0.05");
        BigDecimal b2 = new BigDecimal("0.01");
        System.out.println(b1.add(b2));

    }

}

在我们不了解Bigdecimal前,会以为test1的结果会为 0.06,0.58 ,401.5。

然而,实际却为:BigDecimal使用总结_第1张图片

这可以理解为double 和float 类型的数据过于精确,并没有提供完全精确的结果。

此问题通常会导致在进行商业网站在进行价格计算时,可能顾客有0.6元,在购买价格为0.5和0.1的两件商品时,计算结果0.060000000000000005导致无法购买。

这里我们可以使用BigDemal,下面我们接下来看demo中的Test2,你或许会以为结果如期,然而并不是:

BigDecimal使用总结_第2张图片

这是由于BigDecimal的构造器的使用方法正确,下面我们来看一下BigDecimal的源码定义:

BigDecimal使用总结_第3张图片

大致翻译的意思是:

1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

        2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法

        3、double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

因此Demo中Test3中的使用用法是正确的,运行结果为:




三、工具类的封装

       了解到了bigDecimal的用法,我们可以进一步在项目中封装一个工具类,统一开发规范,供外部调用:

package com.mmall.util;

import java.math.BigDecimal;


/**
 * Created by nice on 2017/8/31.
 */
public class BigDecimalUtil {

    /**
     * 此工具类封装
     * 商业价格计算 需使用BigDecimal类型
     * String 构造器方法
     * 否则会出现精度失误
     */

      private BigDecimalUtil(){

      }

    /**
     * BigDecimal 加法
     * @param v1
     * @param v2
     * @return
     */
      public static BigDecimal add(double v1,double v2){
          BigDecimal b1 = new BigDecimal(Double.toString(v1));
          BigDecimal b2 = new BigDecimal(Double.toString(v2));
          return b1.add(b2);
      }

    /**
     * BigDecimal 减法
     * @param v1
     * @param v2
     * @return
     */
      public static BigDecimal sub(double v1,double v2){
          BigDecimal b1 = new BigDecimal(Double.toString(v1));
          BigDecimal b2 = new BigDecimal(Double.toString(v2));
          return b1.subtract(b2);
      }

    /**
     * BigDecimal 乘法
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal mul(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }


    /**
     * BigDecimal 除法
     * 默认使用div方法的重载
     * 采用保留2位小数 四舍五入
     * 也可自定义规则 使用该方法
     * @param v1
     * @param v2
     * @param scale scale of the {@code BigDecimal} quotient to be returned
     * @param roundingMode  rounding mode to apply.
     * @return
     */
    public static BigDecimal div(double v1,double v2,int scale,int roundingMode){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,scale,roundingMode);
    }


    /**
     * BigDecimal 除法
     * 除法情况可以设置保留位数
     * 及四舍五入的策略
     * 如下 保留两位小数 四舍五入
     * 具体使用可参考Bigdecimal.divide 的重载方法
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal div(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);
    }
    

}


四、总结:

由以上我们可以看出:

 (1)BigDecimal在商业计算需要注意和使用,尤其电商的项目中对于价格的操作尤其敏感。

 (2)尽量使用参数类型为String的构造函数。

   (3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。


你可能感兴趣的:(JAVA,Bigdecimal,商业运算,精度,double,float)