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进行(所有)运算时,尽量指定精度和舍入模式。
相关推荐
百事老饼干11 分钟前
Java[面试题]-真实面试
java·开发语言·面试
customer0819 分钟前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_8575893629 分钟前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
HBryce2432 分钟前
缓存-基础概念
java·缓存
一只爱打拳的程序猿1 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck1 小时前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
白子寰1 小时前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT1 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习