飞道的博客

一文了解BigDecimal全部

333人阅读  评论(0)

以前就有想法花一点时间学习一下Java中用于表示高精度运算的类BigDecimal
了解之后,总结一下以示学习

1. BigDecimal是什么

先看一组例子:

public static void main(String[] s) {
        double a = 0.1, b = 0.2;
        System.out.println(a + b);
    }
输出结果:
0.30000000000000004

事与愿违,结果并不是我们想象的0.3;

原因很简单,计算机在做运算的时候,会先将十进制变成二进制,再对二进制进行运算;在十进制转换为二进制的时候,有时候不能使用有限的一个二进制来表达,尤其是遇到有小数的情况,即出现了精度丢失问题,进而导致运算的时候出现了事与愿违的一幕。如0.1转换为二进制是
0.000110011001100110011001100…………

既然有问题,那就必有解决办法;在Java中是使用BigDecimal 来解决;是java.math包中提供的API类,用来对超过16位有效位的数进行精确的运算。除了加减乘除运算,还提供了格式化,舍入等方法。

2. BigDecimal常用构造方法
  • BigDecimal(int)

    创建一个具有参数所指定整数值的对象

  • BigDecimal(double)

    创建一个具有参数所指定双精度值的对象

  • BigDecimal(long)

    创建一个具有参数所指定长整数值的对象

  • BigDecimal(String)

    创建一个具有参数所指定以字符串表示的数值的对象

问题分析:

public static void main(String[] s) {
        BigDecimal bigDecimal = new BigDecimal("0.1");
        BigDecimal bigDecimal2 = new BigDecimal("0.2");
        BigDecimal bigDecimal3 = new BigDecimal(0.1);  // 传double构造方法
        BigDecimal bigDecimal4 = new BigDecimal(0.2);  // 传double构造方法
        System.out.println(bigDecimal.add(bigDecimal2));
        System.out.println(bigDecimal3.add(bigDecimal4));
    }
输出结果:
0.3
0.3000000000000000166533453693773481063544750213623046875

原因:

  1. 使用BigDecimal类构造方法传入double类型时,计算的结果也是不精确的!因为不是所有的浮点数都能够被精确的表示成一个double
    类型值,有些浮点数值不能够被精确的表示成 double 类型值,因此它会被表示成与它最接近的 double 类型的值。
  2. String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,通常建议优先使用String构造方法。也是官方文档推荐的。
3. BigDecimal常用方法
		BigDecimal bigDecimal = new BigDecimal("0.1");
        BigDecimal bigDecimal2 = new BigDecimal("0.2");
        BigDecimal bigDecimal3 = new BigDecimal("-0.2");
        // 加
        System.out.println(bigDecimal.add(bigDecimal2));
        // 乘
        System.out.println(bigDecimal.multiply(bigDecimal2));
        // 减
        System.out.println(bigDecimal.subtract(bigDecimal2));
        // 除
        System.out.println(bigDecimal.divide(bigDecimal2));
        // 绝对值
        System.out.println(bigDecimal3.abs());
        // 比较大小,-1小于,0等于,1大于
        System.out.println(bigDecimal.compareTo(bigDecimal2));
运行结果:
0.3
0.02
-0.1
0.5
0.2
-1
4. BigDecimal根据舍入模式保留小数位

运算的时候保留小数位,并指定某种具体的保留模式

		/*
         * 保留小数位(2位),以及舍入模式( RoundingMode.HALF_UP:四舍五入)
         */
        BigDecimal bigDecimal = new BigDecimal("0.333");
        BigDecimal bigDecimal2 = new BigDecimal("0.111");
        // 加
        System.out.println(bigDecimal.add(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 乘
        System.out.println(bigDecimal.multiply(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 减
        System.out.println(bigDecimal.subtract(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 除(三分之一四舍五入模式保留二位小数)
        System.out.println(bigDecimal2.divide(bigDecimal, 2, RoundingMode.HALF_UP));
运行结果:
0.44
0.04
0.22
0.33

注意:

  1. 值得一提的是,运用除法的时候,如果出现无限循环小数时,就会抛异常:
    java.lang.ArithmeticException:Non-terminating decimal expansion; no exact representable decimal result.
    解决异常的方法就是设置小数点位数
  2. 加减乘和除设置保留小数方式不一样
  3. 其他的舍入模式可以参见jdk文档(RoundingMode类),这里不做介绍,传送门:http://www.matools.com/api/java8
5. BigDecimal格式化

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制

		// 建立货币格式化引用
		NumberFormat currency = NumberFormat.getCurrencyInstance(); 
		// 建立百分比格式化引用
        NumberFormat percent = NumberFormat.getPercentInstance(); 

        BigDecimal a= new BigDecimal("15.483"); 
        BigDecimal b = new BigDecimal("0.1235"); 
        BigDecimal c= a.multiply(b);

        System.out.println(currency.format(a));
        System.out.println(percent.format(b));
        System.out.println(currency.format(c));
        System.out.println(percent.format(c));
运行结果:
¥15.48
12%
¥1.91
191%

注意:

  1. 选择系统默认的货币格式,且默认保留两位小数(钱就这样哈哈),采用四舍五入模式;
    如需选择其他的货币格式,可选择性的传入Locale里面的常量,如
    NumberFormat currency1 = NumberFormat.getCurrencyInstance(Locale.US) // $美元
  2. 选择系统默认的百分比格式,且默认不保留小数位,采用四舍五入模式;

如需指定保留小数位,可使用setMaximumFractionDigitsgetMinimumFractionDigits设置最大最小的小数位

		// 建立货币格式化引用
        NumberFormat currency = NumberFormat.getCurrencyInstance();
        // 建立百分比格式化引用
        NumberFormat percent = NumberFormat.getPercentInstance();
        // 小数点最多三位,不够也不用补零
        percent.setMaximumFractionDigits(3);

        BigDecimal a = new BigDecimal("15.483");
        BigDecimal b = new BigDecimal("0.1235");
        BigDecimal c = a.multiply(b);

        System.out.println(currency.format(a));
        System.out.println(percent.format(b));
        System.out.println(currency.format(c));
        System.out.println(percent.format(c));
运行结果:
¥15.48
12.35%
¥1.91
191.215%

换成:

		// 小数点最少三位,不够补零
        percent.setMinimumFractionDigits(3);
运行结果:
¥15.48
12.350%
¥1.91
191.215%

提示:

  1. 也可以使用setMaximumIntegerDigits和setMinimumIntegerDigits设置最多和最少整数位,用法和小数位设置一致

除了NumberFormat,也可以使用它的子类DecimalFormat,对Decimal数值进行格式化(保留小数位)

		/*
         * 0:少了用0补,多了切掉,四舍五入
         * #:少了也不补(就空着),多了切掉,四舍五入 
         * 注意:使用#时数值0不能表示一个位置
         */
         DecimalFormat df = new DecimalFormat("0.00");
         System.out.println(df.format(new BigDecimal("3.435")));
         System.out.println(df.format(new BigDecimal(0)));
         System.out.println(df.format(new BigDecimal("0.00")));
         System.out.println(df.format(new BigDecimal("0.001")));
         System.out.println(df.format(new BigDecimal("0.006")));
         System.out.println(df.format(new BigDecimal("10.206")));
         
		 System.out.println("===========");
		 
		 DecimalFormat df1 = new DecimalFormat("0.##");
         System.out.println(df1.format(new BigDecimal("3.435")));
         System.out.println(df1.format(new BigDecimal(0)));
         System.out.println(df1.format(new BigDecimal("0.00")));
         System.out.println(df1.format(new BigDecimal("0.001")));
         System.out.println(df1.format(new BigDecimal("0.006")));
         System.out.println(df1.format(new BigDecimal("10.206")));
运行结果:
3.44
0.00
0.00
0.00
0.01
10.21
=========
3.44
0
0
0
0.01
10.21

注意:

  1. 0和#都是占位作用,是最多位数的作用,区别在于不够时#不补,而0补零
  2. #后面不能有0,因为如果位数不够,#表示不补,0表示补0,这是矛盾的,会报异常
    java.lang.IllegalArgumentException: Unexpected ‘0’ in pattern “0.#0”
6. BigDecimal总结
  • 尽量使用参数类型为String的构造函数
  • 系统涉及高精度运算则一定要使用BigDecimal,如果对精度没有要求,则推荐使用double或float;因为BigDecimal性能低于double和float
  • BigDecimal 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值
  • 使用除法的时候,最好设置好要保留的位数,不能很容易因为无限循环出现异常,导致程序终止
7. BigDecimal工具类推荐

来源于网络,我也在使用的一个BigDecimal工具类

import java.math.BigDecimal;

/**
 * 用于高精确处理常用的数学运算
 */
public class ArithmeticUtils {
    //默认除法保留小数位数
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 提供精确的加法运算
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */

    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 BigDecimal add(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2);
    }

    /**
     * 提供精确的加法运算
     * @param v1    被加数
     * @param v2    加数
     * @param scale 保留scale 位小数
     * @return 两个参数的和
     */
    public static String add(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的减法运算
     * @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 BigDecimal sub(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2);
    }

    /**
     * 提供精确的减法运算
     * @param v1    被减数
     * @param v2    减数
     * @param scale 保留scale 位小数
     * @return 两个参数的差
     */
    public static String sub(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的乘法运算
     * @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();
    }

    /**
     * 提供精确的乘法运算
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static BigDecimal mul(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2);
    }

    /**
     * 提供精确的乘法运算
     * @param v1    被乘数
     * @param v2    乘数
     * @param scale 保留scale 位小数
     * @return 两个参数的积
     */
    public static double mul(double v1, double v2, int scale) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return round(b1.multiply(b2).doubleValue(), scale);
    }

    /**
     * 提供精确的乘法运算
     * @param v1    被乘数
     * @param v2    乘数
     * @param scale 保留scale 位小数
     * @return 两个参数的积
     */
    public static String mul(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
     * 小数点以后10位,以后的数字四舍五入
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */

    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale 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, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示需要精确到小数点以后几位
     * @return 两个参数的商
     */
    public static String div(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v1);
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的小数位四舍五入处理
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static String round(String v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(v);
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取余数
     * @param v1    被除数
     * @param v2    除数
     * @param scale 小数点后保留几位
     * @return 余数
     */
    public static String remainder(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取余数  BigDecimal
     * @param v1    被除数
     * @param v2    除数
     * @param scale 小数点后保留几位
     * @return 余数
     */
    public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 比较大小
     * @param v1 被比较数
     * @param v2 比较数
     * @return 如果v1 大于v2 则 返回true 否则false
     */
    public static boolean compare(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        int bj = b1.compareTo(b2);
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;
        return res;
    }
}

转载:https://blog.csdn.net/shijiujiu33/article/details/106797175
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场