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)

大家都在看

  • 【三】pig4cloud项目一键build — Mysql创建新用户,并外网访问

    Mysql 默认用户 root是禁止外网访问的,而且使用 root外网访问权限太大,不安全, 而且 pig4cloud 程序里面默认配置的是 root,那这里我们把 root用户作…

    Java 2023年6月8日
    081
  • Java多线程系列——计数器 CountDownLatch

    简介: CountDownLatch 是一个非常实用的多线程控制工具类,通常用来控制线程的等待,它可以让某个线程等待直到倒计时结束 CountDownLatch 提供了两个主要的方…

    Java 2023年5月29日
    098
  • Java SE final关键字

    final可以修饰类、属性、方法和局部变量 如下情况,可以使用final 当不希望类被继承时,可以用final修饰 当不希望父类的某个方法被子类覆盖/重写(override)时,可…

    Java 2023年6月7日
    071
  • 《回炉重造》——集合(容器)

    整体框架 绿色代表接口/抽象类;蓝色代表类。 主要由两大接口组成,一个是「Collection」接口,另一个是「Map」接口。 前言 以前刚开始学习「集合」的时候,由于没有好好预习…

    Java 2023年6月10日
    073
  • MySQL学习-idea连接数据库导入jar包

    导包先有包 !!!一定要下载和自己MySQL版本一样的jar包!!! !!!一定要下载和自己MySQL版本一样的jar包!!! !!!一定要下载和自己MySQL版本一样的jar包!…

    Java 2023年6月9日
    0100
  • Windows下安装kubectl及Node和Pod操作常用命令

    kubernetes通过kube-apiserver作为整个集群管理的入口。Apiserver是整个集群的主管理节点,用户通过Apiserver配置和组织集群,同时集群中各个节点同…

    Java 2023年6月7日
    087
  • Java 北京时间 转 UTC时间

    ” 北京时间” 转为” UTC时间“,根据需要转换格式不同,实现方式有所不同。 简单整理如下4种格式: 1、UTC格式:2021-1…

    Java 2023年5月29日
    063
  • 堆的shiftup以及shiftdown

    目录 堆的 shift up Java 实例代码 堆的 shift down Java 实例代码 堆的 shift up 本小节介绍如何向一个最大堆中添加元素,称为 shift u…

    Java 2023年6月5日
    058
  • Hbase初识

    简介 数据模型 相关数据库 典型应用 优势 劣势 key-value Redis 缓存 快速查询 存储数据缺乏结构化 列族 Cassandra,Hbase 分布式的文件系统,大规模…

    Java 2023年6月8日
    086
  • Restful风格

    Restful 1.REST架构的主要原则 1.1 对网络上所有的资源都有一个资源标志符 1.2 对资源的操作不会改变标识符 1.3 同一资源有多种表现形式(xml、json)、 …

    Java 2023年6月14日
    097
  • 看完这个,还不会DVMA,请你吃瓜

    学习渗透测试,特别是 Web 渗透,最头疼的无疑就是寻找靶机环境,通常是不同的漏洞需要找不同的靶机源码,而不同的源码通常 Web 架构又不一样,所以要找到一套能够练习所有 Web …

    Java 2023年6月8日
    084
  • JVM诊断命令jcmd介绍

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介 从JDK7开始,jdk提供了一个方便扩展的诊断命令jcmd,用来取代之前比较分散的jdk基础命…

    Java 2023年6月7日
    083
  • Redis之ziplist源码分析

    一、ziplist简介 从上一篇分析我们知道quicklist的底层存储使用了ziplist(压缩列表),由于压缩列表本身也有不少内容,所以重新开了一篇,在正式源码之前,还是先看下…

    Java 2023年6月6日
    092
  • 引路蜂地图API:Gis.Location包定义

    本包定义了GPS接收器一个通用接口,并提供对NMEA 2.0数据的解码方法。在Java ME平台上对JSR179 进行了封装. Coordinates 定义地址经纬度坐标。 Loc…

    Java 2023年5月30日
    0108
  • CentOS7 怎么解决连网问题

    CentOS7 怎么解决连网问题 VmWare安装centos7无法上网怎么办?下面为大家分享了解决方法,供大家参考,具体内容如下 1.关闭防火墙 systemctl stop f…

    Java 2023年6月9日
    081
  • ElasticSearch(一)

    官网: https://www.elastic.co/cn/elasticsearch/ 是什么? Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引…

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