BigDecimal类加减乘除及精度控制

BigDecimal类的应用

文章目录

  • BigDecimal类的应用
    • 为什么要用BigeDecimal类?
    • API
      • 构造器:
      • 函数
    • BigDecimal精度也会丢失?
    • 正确使用BigDecimal
    • 基本的加减乘除运算
    • 除法divide()函数舍入模式使用
    • 舍入模式
      • 1、ROUND_UP
      • 2、ROUND_DOWN
      • 3、ROUND_CEILING
      • 4、ROUND_FLOOR
      • 5、ROUND_HALF_UP
      • 6、ROUND_HALF_DOWN
      • 7、ROUND_HALF_EVEN
      • 8、ROUND_UNNECESSARY
      • 8、ROUND_UNNECESSARY

摘要

​ 前几天写项目遇到了关于金额的精确计算,再次认识到了BigDecimal类,关于BigDecimal意外的踩了很多坑,再次整理一下,方便以后采坑的时候看,也希望可以帮助到别人

为什么要用BigeDecimal类?

​ 原来的时候我也有这个疑问,Java中已经有了 float和double这种精度的小数,为什么还需要BigDecimal呢?接下来我们看个例子:

			System.out.println(0.05 + 0.01);
			
			输出结果:
			0.060000000000000005	

​ 可以看到在Java中进行浮点数运算的时候,会出现精度丢失的问题,那么我们在进行商品价格计算的时候,就会出现问题,很有可能造成我们手中有0.06元,却无法购买一个0.05元和一个0.01元的东西。因为如上所示,两个和不为0.06.尤其涉及到钱的问题的时候,出现的问题将会是巨大的,可能会导致无法下单,对账等问题,所以我们要用BigeDecimal类来解决精度丢失的问题。

​ **注:**Java中float的精度为6-7位有效数字,double类型有效数字为15-16位。

API

构造器:

  构造器                   描述                      
  BigDecimal(int)       创建一个具有参数所指定整数值的对象。      
  BigDecimal(double)    创建一个具有参数所指定双精度值的对象。     
  BigDecimal(long)      创建一个具有参数所指定长整数值的对象。     
  BigDecimal(String)    创建一个具有参数所指定以字符串表示的数值的对象。

函数

  方法                    描述                         
  add(BigDecimal)       BigDecimal对象中的值相加,然后返回这个对象。
  subtract(BigDecimal)  BigDecimal对象中的值相减,然后返回这个对象。
  multiply(BigDecimal)  BigDecimal对象中的值相乘,然后返回这个对象。
  divide(BigDecimal)    BigDecimal对象中的值相除,然后返回这个对象。
  toString()            将BigDecimal对象的数值转换成字符串。    
  doubleValue()         将BigDecimal对象中的值以双精度数返回。   
  floatValue()          将BigDecimal对象中的值以单精度数返回。   
  longValue()           将BigDecimal对象中的值以长整数返回。    
  intValue()            将BigDecimal对象中的值以整数返回。 

BigDecimal精度也会丢失?

BigDecimal类加减乘除及精度控制_第1张图片

​ 可以看到BigDecimal精度丢失的更狠,但是在使用String类型的构造器的时候运算却没有出现精度丢失这种问题。计算机的编码格式决定了这样的结果。long可以准确存储19位数字,而double只能预备存储19位数字,double有exp位,可以存储16位以上的数字,但是需要以低位的不精确作为代价。float和double一般用来做科学计算,商业的运算中,我们要使用BigDecimal,我们使用BigDecimal时,一定要使用它的BigDecimal(String)构造方法创建对象才有意义,如果使用其他的,还是有可能会发生精度丢失的情况;

​ 而且我们从源码的注释中也看到了注释,下面是double类型的构造器是的一部分说明:

* The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.
     * This is because 0.1 cannot be represented exactly as a
     * {@code double} (or, for that matter, as a binary fraction of
     * any finite length).  Thus, the value that is being passed
     * in to the constructor is not exactly equal to 0.1,
     * appearances notwithstanding.
       ……
        * When a {@code double} must be used as a source for a
     * {@code BigDecimal}, note that this constructor provides an
     * exact conversion; it does not give the same result as
     * converting the {@code double} to a {@code String} using the
     * {@link Double#toString(double)} method and then using the
     * {@link #BigDecimal(String)} constructor.  To get that result,
     * use the {@code static} {@link #valueOf(double)} method.
     * 
public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}

一看是英文?看不懂?没关系,我们 c 下来 然后再 v 翻译

BigDecimal类加减乘除及精度控制_第2张图片

第一段说的意思大概是只能计算出无线接近这个数,但是无法精确到这个数。第二段则说,如果想要精确计算换个值,那么就把double转为String,并使用String类型的构造方法进行构造,获取结果。

正确使用BigDecimal

​ BigDecimal创建的是对象,我们不能使用传统的 ±/* 运算符对其对象进行数字运算,必须调用其方法,方法中的参数也必须是BigDecimal的对象,我们可以看一下上面的Api

​ 在一般的开发过程中,我们数据库中存储的 decimal 类型就是对应我们的 BigDecimal ,但是有时候我们会碰到数据库中存储的是float和double类型的,在进行转换来转换去的运算,很不方便。这里有一个工具类,该工具类提供了double类型的基本运算,直接CV用就行

/**
 * @author: xuanxuan
 * @date: 2019年9月16日22点10分.
 */
public class BigDecimalUtil {

    private BigDecimalUtil() {

    }

    public static BigDecimal add(double v1, double v2) {// v1 + v2
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }

    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);
    }

    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);
    }

    public static BigDecimal div(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 2 = 保留小数点后两位   ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况
    }
}

基本的加减乘除运算

import java.math.BigDecimal;

/**
 *	燕子南飞去,十里不回头
 */
public class BigDecimalTest {
	public static void main(String[] args) {
		 
        BigDecimal num1 = new BigDecimal(0.005);
        BigDecimal num2 = new BigDecimal(1000000);
        BigDecimal num3 = new BigDecimal(-1000000);
        //尽量用字符串的形式初始化
        BigDecimal num12 = new BigDecimal("0.005");
        BigDecimal num22 = new BigDecimal("1000000");
        BigDecimal num32 = new BigDecimal("-1000000");
 
        //加法
        BigDecimal result1 = num1.add(num2);
        BigDecimal result12 = num12.add(num22);
        //减法
        BigDecimal result2 = num1.subtract(num2);
        BigDecimal result22 = num12.subtract(num22);
        //乘法
        BigDecimal result3 = num1.multiply(num2);
        BigDecimal result32 = num12.multiply(num22);
        //绝对值
        BigDecimal result4 = num3.abs();
        BigDecimal result42 = num32.abs();
        //除法
        BigDecimal result5 = num2.divide(num1,20,BigDecimal.ROUND_HALF_UP);
        BigDecimal result52 = num22.divide(num12,20,BigDecimal.ROUND_HALF_UP);
 
        System.out.println("加法用value结果:"+result1);
        System.out.println("加法用string结果:"+result12);
 
        System.out.println("减法value结果:"+result2);
        System.out.println("减法用string结果:"+result22);
 
        System.out.println("乘法用value结果:"+result3);
        System.out.println("乘法用string结果:"+result32);
 
        System.out.println("绝对值用value结果:"+result4);
        System.out.println("绝对值用string结果:"+result42);
 
        System.out.println("除法用value结果:"+result5);
        System.out.println("除法用string结果:"+result52);
    }
}

除法divide()函数舍入模式使用

使用除法函数的时候,我们要设置参数(设入模式),不然会报错如下:

    BigDecimal num1 = new BigDecimal("10");
    BigDecimal num2 = new BigDecimal("3");
    BigDecimal resultBig = num1.divide(num2);
    System.out.println(resultBig);
    
    输出结果:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
	at java.math.BigDecimal.divide(Unknown Source)
	at BigDecimalTest.main(BigDecimalTest.java:12)

舍入模式

舍入模式一共有八种,常用的有两种:

1、ROUND_UP

​ 舍入远离0的舍入模式,在丢失非零部分之前使用增加数字,始终对非零舍弃部分勤勉的数字加1,此模式不会减少计算值的大小

        BigDecimal num1 = new BigDecimal("10");
        BigDecimal num2 = new BigDecimal("3");
        BigDecimal resultBig = num1.divide(num2,3,BigDecimal.ROUND_UP);
        System.out.println(resultBig);
        
        输出结果:3.334

2、ROUND_DOWN

​ 接入零的舍入模式,在丢弃某部分之前始终不增加数字,从不对舍弃前面的数字加1,直接截断,此模式不会增加计算值的大小。

    BigDecimal num1 = new BigDecimal("10");
    BigDecimal num2 = new BigDecimal("3");
    BigDecimal resultBig = num1.divide(num2,3,BigDecimal.ROUND_DOWN);
    System.out.println(resultBig);

	输出结果:3.333

常用的为以上两种

3、ROUND_CEILING

向正无穷的方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致。注意:此模式不会减少数值大小。

4、ROUND_FLOOR

向负无穷的方向舍入。如果为正数,舍入结果同ROUND_DOWN一致;如果为负数,舍入结果同ROUND_UP一致。注意:此模式不会增加数值大小。

5、ROUND_HALF_UP

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分>= 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。这种模式也就是我们常说的我们的“四舍五入”。

6、ROUND_HALF_DOWN

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。如果舍弃部分> 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。这种模式也就是我们常说的我们的“五舍六入”。

7、ROUND_HALF_EVEN

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则相邻的偶数舍入。如果舍弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。注意:在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况,如果前一位为奇数,则入位,否则舍去。

8、ROUND_UNNECESSARY

断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。注意:在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况,如果前一位为奇数,则入位,否则舍去。

8、ROUND_UNNECESSARY

断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

你可能感兴趣的:(JavaSE基础)