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

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进行(所有)运算时,尽量指定精度和舍入模式。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全真王重阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值