BigDecimal详解和精度问题

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 来定义浮点数的值,然后再进行浮点数的运算操作即可。

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 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal详解和精度问题

加减乘除

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详解和精度问题

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。

BigDecimal详解和精度问题

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 加入了小数位的概念。

Original: https://www.cnblogs.com/javaguide/p/16624548.html
Author: JavaGuide
Title: BigDecimal详解和精度问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/592762/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 四、mybatis动态sql

    前言 前面mysql都是通过静态sql进行查询的,但是如果业务复杂的时候,我们会遇到引号问题,或者多一个空格,这就使得sql代码编写错误了,所以为了解决这个问题,我们有了动态sql…

    Java 2023年6月16日
    0102
  • 4.JSP共有以下9个内置的对象

    request 用户端请求,此请求会包含来自GET/POST请求的参数response 网页传回用户端的回应pageContext 网页的属性是在这里管理session 与请求有关…

    Java 2023年6月9日
    0142
  • 3-面向对象(3)

    一、main()的使用说明 main()方法作为程序的入口 main()方法也是一个普通的静态方法 main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner) 如…

    Java 2023年6月7日
    089
  • 类与对象(一)

    类和对象之间的关系 类是对象的设计图,对象是类的实体 类是对象的模板,对象是类的实例 设计的基本步骤 发现类,根据对象去发现类 找出属性(名词),根据对象的共同属性 找出行为(动词…

    Java 2023年6月6日
    098
  • Windows 下 Nginx 配置 多个conf 文件的过程与注意事项

    Windows 下配置 Nginx 使用多个conf 文件的Include 路径与Linux 有所不同。 如上: Windows : include ../conf.d/*.con…

    Java 2023年5月30日
    083
  • Docker安装容器示例

    1.以mysql为例 ,搜索镜像 2.下载镜像 3.查看已经安装的镜像 4.删除镜像 二、容器操作 软件镜像——运行镜像——产生一个容器(正在运行的软件) 1.运行创建镜像,有了镜…

    Java 2023年6月13日
    075
  • 日志文件

    Java 2023年6月16日
    075
  • JDBC:加载数据库驱动、连接数据库(详细讲解)

    加载数据库驱动: 1)由于Java是一个纯面向对象语言,任何事物在其中都必须抽象成类或者类对象,数据库也不例外,JDBC同样也把数据库抽象成面向对象的结构; 2)JDBC将整个数据…

    Java 2023年6月5日
    0118
  • IO流整的明明白白!

    ​ File课理解为文件和文件夹(目录),用于表示磁盘中某个文件或文件夹的路径。该类包含了文件的创建、删除、重命名、判断是否存在等方法。 ​ 只能设置和获取文件本身的信息(文件大小…

    Java 2023年6月7日
    0122
  • DataTable 将一列转为List

    c# linq用起来特方便,因此我们习惯性的用list来操作。 这里我们将 DataTable 一列转为List: List homeworkIdList = (from r in…

    Java 2023年6月9日
    072
  • Day10

    package array;import java.util.Arrays;public class ArrayDemo6 { public static void main(St…

    Java 2023年6月5日
    076
  • Day9

    package array;public class ArrayDemo1 { //变量类型 变量的名字 = 变量的值 //数组类型 public static void main…

    Java 2023年6月5日
    057
  • SpringBoot集成文件-如何使用POI导出Word文档?

    前文我们介绍了通过Apache POI导出excel,而Apache POI包含是操作Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的J…

    Java 2023年6月6日
    0103
  • MySQL递归查询语法

    业务上有一个递归查询数据表进行累加计算的需求,实现方式上有函数、SQL语句等多种方式,最后选择了SQL方式,如下: <select id="selectChildr…

    Java 2023年6月13日
    071
  • 保姆教程系列二、Nacos实现注册中心

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 上篇我们介绍到 保姆教程系列一、Linux搭建Nacos 注册中心原理 一、环境准备 Java版本:1.8+ (L…

    Java 2023年6月5日
    0104
  • 【转载】SpringCloud-Eurek 心跳阈值说明

    在使用eureka过程中,查看监控界面,出现: Renews threshold:server期望在每分钟中收到的心跳次数Renews (last min):上一分钟内收到的心跳次…

    Java 2023年5月29日
    069
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球