首页 > 其他分享 >BigDecimal详解和精度问题

BigDecimal详解和精度问题

时间:2022-08-25 16:00:51浏览次数:77  
标签:BigDecimal 浮点数 System 详解 println new 精度 out

JavaGuide :「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。

BigDecimal 是大厂 Java 面试常问的一个知识点。

《阿里巴巴 Java 开发手册》中提到:“为了避免精度丢失,可以使用 BigDecimal 来进行浮点数的运算”。

浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。

就比如说十进制下的 0.2 就没办法精确转换成二进制小数:

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。

通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了。

想要解决浮点数运算精度丢失这个问题,可以直接使用 BigDecimal 来定义浮点数的值,然后再进行浮点数的运算操作即可。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);

System.out.println(x.compareTo(y));// 0

BigDecimal 常见方法

创建

我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.add(b));// 1.9
System.out.println(a.subtract(b));// 0.1
System.out.println(a.multiply(b));// 0.90
System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) {
    return divide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

public enum RoundingMode {
   // 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -2 , -2.5 -> -3
			 UP(BigDecimal.ROUND_UP),
   // 2.5 -> 2 , 1.6 -> 1
   // -1.6 -> -1 , -2.5 -> -2
			 DOWN(BigDecimal.ROUND_DOWN),
			 // 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -1 , -2.5 -> -2
			 CEILING(BigDecimal.ROUND_CEILING),
			 // 2.5 -> 2 , 1.6 -> 1
   // -1.6 -> -2 , -2.5 -> -3
			 FLOOR(BigDecimal.ROUND_FLOOR),
   	// 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -2 , -2.5 -> -3
			 HALF_UP(BigDecimal.ROUND_HALF_UP),
   //......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 等值比较问题

《阿里巴巴 Java 开发手册》中提到:

BigDecimal 使用 equals() 方法进行等值比较出现问题的代码示例:

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.equals(b));//false

这是因为 equals() 方法不仅仅会比较值的大小(value)还会比较精度(scale),而 compareTo() 方法比较的时候会忽略精度。

1.0 的 scale 是 1,1 的 scale 是 0,因此 a.equals(b) 的结果是 false。

compareTo() 方法可以比较两个 BigDecimal 的值,如果相等就返回 0,如果第 1 个数比第 2 个数大则返回 1,反之返回-1。

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.compareTo(b));//0

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

标签:BigDecimal,浮点数,System,详解,println,new,精度,out
From: https://www.cnblogs.com/javaguide/p/16624548.html

相关文章

  • DNS 查询原理详解
    通过DNS查询,得到域名的IP地址,才能访问网站。那么,DNS查询到底是怎么完成的?本文通过实例,详细介绍背后的步骤。   一、DNS服务器 域名对应的IP地址,都保......
  • TDengine 3.0 三大创新详解
    在8月13日的 TDengine 开发者大会上,涛思数据创始人陶建辉进行了题为《高性能、云原生的极简时序数据处理平台》的主题演讲。在本次演讲中,他不仅分享了时序数据库现......
  • 详解MySQL隔离级别
    一个事务具有ACID特性,也就是(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),本文主要讲解一下其中的Isolation,也就是事务的隔离性。概......
  • js加减乘除--科学计数法-解决精度丢失
    'usestrict';Object.defineProperty(exports,'__esModule',{value:true});/***@desc解决浮动运算问题,避免小数点后产生多位数和计算精度损失。*问题示例......
  • Java8 对list集合中的bigdecimal进行分组求和,均值,最大值,最小值
     文章目录需求中对数值进行求和的非常多,但java8对bigdecimal求和没有封装新建接口ToBigDecimalFunction新建工具类CollectorsUtil实体类Person 需求中对......
  • Monkey测试详解
    一、测试工具Monkey是什么?Monkey是AndroidSDK提供的一个命令行工具,可以简单,方便地运行在任何版本的Android模拟器或实体设备上。Monkey就是猴子,Monkey测试,是指像猴子一样......
  • GET 和 POST详解
    https://blog.csdn.net/qq_44204058/article/details/113984363一、HTTP请求方法Http协议定义了很多与服务器交互的方法,最基本的有4种,分别是GET,POST,PUT,DELETE.一个URL......
  • Object.defineProperty方法详解(全面)
     Object.defineProperty方法详解(全面) 一、Object.defineProperty的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性二、Object.defineproperty方......
  • java泛型详解
    java泛型详解1.泛型​ Java泛型是J2SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(typeparameter)这种参数类型可以用在类、接......
  • 定时任务报警通知解决方案详解
    简介: 本文详细介绍定时任务通知的解决方案,以及市面上常见的开源定时任务通知方案对比。什么是定时任务定时任务是每个业务常见的需求,比如每分钟扫描超时支付的......