Java BigDecimal详解

目录

一、前言

二、BigDecimal

2.1、常用构造方法

2.2、常用方法

2.3、示例代码

add(BigDecimal)方法

subtract(BigDecimal)方法

multiply(BigDecimal)方法

divide(BigDecimal)方法

max(BigDecimal val)方法

2.4、BigDecimal的八种舍入模式

setScale(1)

setScale(1,BigDecimal.ROUND_DOWN)

setScale(1,BigDecimal.ROUND_UP)

setScale(1,BigDecimal.ROUND_HALF_UP)

setScaler(1,BigDecimal.ROUND_HALF_DOWN)

setScaler(1,BigDecimal.ROUND_CEILING)

setScaler(1,BigDecimal.ROUND_FLOOR)

setScaler(1,BigDecimal.ROUND_HALF_EVEN)

2.5、注意事项

(1)创建 BigDecimal精度丢失的坑

(2)等值比较的坑

(3)无限精度的坑


一、前言

我们在做浮点数运算时,很多时间计算的结果并不是我们想要的,比如下面的代码:

double y = 1.0 - 0.9;
System.out.println(y);

正常来说,我们想要的结果为0.1,但结果确是:

0.09999999999999998

我们发现,计算出来的值和我们预期结果不一致。原因在于我们的计算机是二进制的。浮点数没有办法使用二进制进行精确表示

计算机的 CPU 表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度,有些浮点数运算也会产生一定的误差。

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float 和 double 作精确运算的时候要特别小心。

二、BigDecimal

因为浮点类型无法满足我们的精度要求,在做金融行业运算时,我们尽量使用BigDecimal类。

2.1、常用构造方法

                构造方法                                               含义
BigDecimal(double val) 创建一个具有参数所指定双精度值的对象。(不推荐使用,因为存在精度丢失问题)
BigDecimal(String val) 创建一个具有参数所指定以字符串表示的数值的对象。(推荐使用)

2.2、常用方法

                  方法                                                含义
add(BigDecimal) BigDecimal对象中的值相加,返回BigDecimal对象
subtract(BigDecimal) BigDecimal对象中的值相减,返回BigDecimal对象
multiply(BigDecimal) BigDecimal对象中的值相乘,返回BigDecimal对象
divide(BigDecimal) BigDecimal对象中的值相除,返回BigDecimal对象
abs() 将BigDecimal对象中的值转换成绝对值
doubleValue() 将BigDecimal对象中的值转换成双精度数
floatValue() 将BigDecimal对象中的值转换成单精度数
longValue() 将BigDecimal对象中的值转换成长整数
intValue() 将BigDecimal对象中的值转换成整数
compareTo(BigDecimal val) 比较大小,返回int类型。0(相等) 1(大于) -1(小于)
toString() 有必要时使用科学计数法。
toPlainString() 不使用任何指数(推荐使用)
toEngineeringString() 有必要时使用工程计数法。 工程记数法是一种工程计算中经常使用的记录数字的方法,与科学技术法类似,但要求10的幂必须是3的倍数
max(BigDecimal val) 两值比较,返回最大值
negate() 求相反数,正变负,负变正
pow(int n) 求乘方,如BigDecimal.valueOf(2).pow(3)的值为8

2.3、示例代码

add(BigDecimal)方法

BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("0.9");
BigDecimal b3 = b1.add(b2);
double d1 = b3.doubleValue();
System.out.println(d1);  //1.9

subtract(BigDecimal)方法

BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("0.9");
BigDecimal b3 = b1.subtract(b2);
double d1 = b3.doubleValue();
System.out.println(d1);  //0.1

multiply(BigDecimal)方法

BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("0.9");
BigDecimal b3 = b1.multiply(b2);
double d1 = b3.doubleValue();
System.out.println(d1);  //0.9

divide(BigDecimal)方法

BigDecimal b1 = new BigDecimal("2.0");
BigDecimal b2 = new BigDecimal("1.0");
BigDecimal b3 = b1.divide(b2);
double d1 = b3.doubleValue();
System.out.println(d1);  //2.0

max(BigDecimal val)方法

BigDecimal b1 = new BigDecimal("2.0");
BigDecimal b2 = new BigDecimal("1.0");
BigDecimal b3 = b1.max(b2);
double d1 = b3.doubleValue();
System.out.println(d1);  //2.0

2.4、BigDecimal的八种舍入模式

setScale(1)

表示保留一位小数,默认用四舍五入方式。

BigDecimal b1 = new BigDecimal("2.6789");
BigDecimal b2 = b1.setScale(1);
System.out.println(b2.doubleValue());

Java BigDecimal详解_第1张图片

setScale方法默认使用的roundingMode是ROUND_UNNECESSARY,不需要使用舍入模式,设置精度2位,但是小数点后有4位肯定会抛异常。

setScale(1,BigDecimal.ROUND_DOWN)

直接删除多余的小数位,如2.35会变成2.3。

BigDecimal b1 = new BigDecimal("2.6789");
BigDecimal b2 = b1.setScale(1,BigDecimal.ROUND_DOWN);
System.out.println(b2.doubleValue()); //2.6

setScale(1,BigDecimal.ROUND_UP)

进位处理,2.35变成2.4。

BigDecimal b1 = new BigDecimal("2.6789");
BigDecimal b2 = b1.setScale(1,BigDecimal.ROUND_UP);
System.out.println(b2.doubleValue()); //2.7

setScale(1,BigDecimal.ROUND_HALF_UP)

四舍五入,2.35变成2.4。

BigDecimal b1 = new BigDecimal("2.6789");
BigDecimal b2 = b1.setScale(1,BigDecimal.ROUND_HALF_UP);
System.out.println(b2.doubleValue()); //2.7

setScaler(1,BigDecimal.ROUND_HALF_DOWN)

四舍五入,2.35变成2.3,如果是5则向下舍。

BigDecimal b1 = new BigDecimal("2.35");
BigDecimal b2 = b1.setScale(1,BigDecimal.ROUND_HALF_DOWN);
System.out.println(b2.doubleValue()); //2.3

setScaler(1,BigDecimal.ROUND_CEILING)

接近正无穷大的舍入。

BigDecimal b1 = new BigDecimal("2.35");
BigDecimal b2 = b1.setScale(1,BigDecimal.ROUND_CEILING);
System.out.println(b2.doubleValue()); //2.4

setScaler(1,BigDecimal.ROUND_FLOOR)

接近负无穷大的舍入,数字>0和ROUND_UP作用一样,数字<0和ROUND_DOWN作用一样。

setScaler(1,BigDecimal.ROUND_HALF_EVEN)

向最接近的数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。

2.5、注意事项

(1)创建 BigDecimal精度丢失的坑

在BigDecimal 中提供了多种创建方式,可以通过new 直接创建,也可以通过 BigDecimal.valueOf 创建。这两种方式使用不当,也会导致精度问题。如下:

public static void main(String[] args) throws Exception {
   BigDecimal b1 = new BigDecimal(0.1);
   System.out.println(b1);
   BigDecimal b2 = BigDecimal.valueOf(0.1);
   System.out.println(b2);
   BigDecimal b3 = BigDecimal.valueOf(0.111111111111111111111111111234);
   System.out.println(b3);
}

执行结果:

0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111

上面示例中两个方法都传入了double类型的参数0.1但是 b1 还是出现了精度的问题。造成这种问题的原因是 0.1 这个数字计算机是无法精确表示的,送给 BigDecimal 的时候就已经丢精度了,而 BigDecimal.valueOf 的实现却完全不同。如下源码所示,BigDecimal.valueOf 中是把浮点数转换成了字符串来构造的BigDecimal,因此避免了问题。

public static BigDecimal valueOf(double val) {
   return new BigDecimal(Double.toString(val));
}

结论:

  • 在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型。
  • 如果无法满足第一条,则可采用BigDecimal.valueOf方法来构造初始化值(但是valueOf受double类型精度影响,当传入参数小数点后的位数超过double允许的16位精度还是可能会出现问题的)。

(2)等值比较的坑

一般在比较两个值是否相等时,都是用equals 方法,但是,在BigDecimal 中使用equals可能会导致结果错误,BigDecimal 中提供了 compareTo 方法,在很多时候需要使用compareTo 比较两个值。如下所示:

public static void main(String[] args){
    BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("1.00");
    System.out.println(b1.equals(b2));
    System.out.println(b1.compareTo(b2));
}

执行结果:

false
0

出现此种结果的原因是,equals不仅比较了值是否相等,还比较了精度是否相同。示例中,由于两个值的精度不同,所有结果也就不相同。而 compareTo 是只比较值的大小。返回的值为-1(小于),0(等于),1(大于)。

结论:

  • 如果比较两个BigDecimal值的大小,采用其实现的compareTo方法;
  • 如果严格限制精度的比较,那么则可考虑使用equals方法。

(3)无限精度的坑

BigDecimal 并不代表无限精度,当在两个数除不尽的时候,就会出现无限精度的坑,如下所示:

public static void main(String[] args){
    BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("3.0");
    b1.divide(b2);
}

执行结果:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
	at java.math.BigDecimal.divide(BigDecimal.java:1693)
	at com.demo.controller.Test.main(Test.java:29)

大致意思就是,如果在除法(divide)运算过程中,如果商是一个无限小数(如 0.333…),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。

此种情况,只需要在使用 divide方法时指定结果的精度即可:

public static void main(String[] args){
   BigDecimal b1 = new BigDecimal("1.0");
   BigDecimal b2 = new BigDecimal("3.0");
   System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));//0.33
}

结论:

  • 在使用BigDecimal进行(所有)运算时,尽量指定精度和舍入模式。

你可能感兴趣的:(Java知识点,java,开发语言,BigDecimal,浮点类型,丢失精度)