Java BigDecimal详解

目录

一、前言

二、BigDecimal

2.1、常用构造方法

2.2、常用方法

2.3、示例代码

add(BigDecimal)方法

subtract(BigDecimal)方法

multiply(BigDecimal)方法

divide(BigDecimal)方法

[max(BigDecimal val)方法](#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精度丢失的坑](#(1)创建 BigDecimal精度丢失的坑)

(2)等值比较的坑

(3)无限精度的坑


一、前言

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

java 复制代码
double y = 1.0 - 0.9;
System.out.println(y);

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

java 复制代码
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)方法

java 复制代码
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)方法

java 复制代码
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)方法

java 复制代码
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)方法

java 复制代码
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)方法

java 复制代码
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)

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

java 复制代码
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。

java 复制代码
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。

java 复制代码
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。

java 复制代码
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则向下舍。

java 复制代码
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)

接近正无穷大的舍入。

java 复制代码
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 创建。这两种方式使用不当,也会导致精度问题。如下:

java 复制代码
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);
}

执行结果:

java 复制代码
0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111

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

java 复制代码
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 比较两个值。如下所示:

java 复制代码
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));
}

执行结果:

java 复制代码
false
0

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

结论:

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

(3)无限精度的坑

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

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

执行结果:

java 复制代码
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方法时指定结果的精度即可:

java 复制代码
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进行(所有)运算时,尽量指定精度和舍入模式。
相关推荐
zquwei10 分钟前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
TT哇16 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
火烧屁屁啦39 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
飞飞-躺着更舒服43 分钟前
【QT】实现电子飞行显示器(改进版)
开发语言·qt
w_31234541 小时前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S2 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring