BigDecimal的常见陷阱

文章目录

  • BigDecimal概述
  • BigDecimal常见陷阱
    • 1.使用BigDecimal的构造函数传入浮点数
    • 2.使用equals()方法进行数值比较
    • 3.使用不正确的舍入模式
  • 总结:

BigDecimal概述

BigDecimal 是 Java 中的一个类,用于精确表示和操作任意精度的十进制数。它提供了高精度的数值计算,并且可以避免浮点数计算中常见的精度丢失问题。
它提供了大量的方法来支持基本的数学运算,如加法、减法、乘法、除法等。它还支持比较操作和取整操作,可以设置小数位数、舍入模式等。此外,BigDecimal 还提供了一些其他功能,如转换为科学计数法、格式化输出、判断是否是整数等。
适用场景:需要处理精确计算或防止浮点数计算精度丢失的场景。

BigDecimal常见陷阱

1.使用BigDecimal的构造函数传入浮点数

其实这个问题我们在使用Float、Double等浮点类型进行计算时,也会经常遇到,比如说下面这个代码

@Test
public void bigDecimalDemo1() {float float1 = 1;float float2 = 0.9f;System.out.println(float1 - float2);
}

输出结果是多少呢?0.1?不是,输出结果是0.100000024。因为 0.9 无法被精确表示为有限位数的二进制小数。在转换为二进制时可能会产生近似值。因此,在进行减法运算时,实际上是对近似值进行计算,而不是对准确的 0.9 进行计算。这导致了精度丢失,最终的计算结果也是一个近似值。因此,输出结果不是准确的 0.1,而是一个近似值。
小伙伴肯定能想到使用BigDecimal来避免这个问题,这时候第一个需要避免的陷阱就来了。看以下代码:

@Test
public void bigDecimalDemo2(){BigDecimal bigDecimal1 = new BigDecimal(0.01);BigDecimal bigDecimal2 = BigDecimal.valueOf(0.01);System.out.println("bigDecimal1 = " + bigDecimal1);System.out.println("bigDecimal2 = " + bigDecimal2);
}

输出结果如下:

bigDecimal1 = 0.01000000000000000020816681711721685132943093776702880859375
bigDecimal2 = 0.01

观察输出结果我们可以知道,使用BigDecimal时同样会有精度的问题。所以我们在创建BigDecimal对象时,有初始值使用BigDecimal.valueOf()的方式,可以避免出现精度问题。

为什么会出现差异?

在使用new BigDecimal()实际上是将 0.01 转换为二进制近似值,并将其存储为 BigDecimal 对象。因此,结果中存在微小的误差,即输出结果为0.01000000000000000020816681711721685132943093776702880859375。
而BigDecimal.valueOf()不同,其内部是先将double转为String,因此不存在精度问题。

public static BigDecimal valueOf(double val) {// Reminder: a zero double returns '0.0', so we cannot fastpath// to use the constant ZERO.  This might be important enough to// justify a factory approach, a cache, or a few private// constants, later.return new BigDecimal(Double.toString(val));
}

TIPS:

  1. 使用整数或长整数作为参数构造:
    ○ BigDecimal(int val):使用一个 int 类型的整数值创建 BigDecimal。
    ○ BigDecimal(long val):使用一个 long 类型的整数值创建 BigDecimal。
  2. 使用字符串作为参数构造:
    ○ BigDecimal(String val):使用一个字符串表示的数值创建 BigDecimal。该字符串可以包含整数部分、小数部分和指数部分。
  3. 使用双精度浮点数作为参数构造:
    ○ BigDecimal(double val):使用一个 double 类型的浮点数值创建 BigDecimal。注意,由于浮点数精度可能丢失,建议使用字符串或其他方法构造 BigDecimal,以避免精度损失问题。
  4. 使用基于 BigInteger 的构造方法:
    ○ BigDecimal(BigInteger val):使用一个 BigInteger 对象来创建 BigDecimal。

2.使用equals()方法进行数值比较

日常项目我们是如何进行BigDecimal数值比较呢?使用equals方法还是compareTo方法?如果使用的是equals方法,那就需要注意啦。看一下示例:

@Test
public void bigDecimalDemo3(){BigDecimal bigDecimal1 = new BigDecimal("0.01");BigDecimal bigDecimal2 = new BigDecimal("0.010");System.out.println(bigDecimal1.equals(bigDecimal2));System.out.println(bigDecimal1.compareTo(bigDecimal2));
}

输出结果如下:

false
0

观察结果可以知道使用equals比较结果是不相等的;compareTo的结果为0代表两个数相等;
● compareTo实现了Comparable接口,比较的是值的大小,返回的值为-1-小于,0-等于,1-大于。

为什么equals返回的是false?

public boolean equals(Object x) {if (!(x instanceof BigDecimal))return false;BigDecimal xDec = (BigDecimal) x;if (x == this)return true;if (scale != xDec.scale)return false;long s = this.intCompact;long xs = xDec.intCompact;if (s != INFLATED) {if (xs == INFLATED)xs = compactValFor(xDec.intVal);return xs == s;} else if (xs != INFLATED)return xs == compactValFor(this.intVal);return this.inflated().equals(xDec.inflated());
}

我们观察equals的实现逻辑可以知道,BigDecimal重写了equals方法,重写后的关键代码:

if (scale != xDec.scale)return false;

也就是会比较两个数值的精度,精度不同返回false。

3.使用不正确的舍入模式

使用BigDecimal进行运算时,一定要正确的使用舍入模式,避免舍入误差引起的问题,并且有时候出现结果是无限小数,程序会抛出异常,比如说:

@Test
public void bigDecimalDemo4(){BigDecimal bigDecimal1 = new BigDecimal("1.00");BigDecimal bigDecimal2 = new BigDecimal("3.00");BigDecimal bigDecimal3 = bigDecimal1.divide(bigDecimal2);System.out.println(bigDecimal3);
}

输出结果如下:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

简单的来说,如果在除法运算过程中,其结果是一个无限小数,而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。
此时,我们只要正确指定结果精度即可:

@Test
public void bigDecimalDemo4(){BigDecimal bigDecimal1 = new BigDecimal("1.00");BigDecimal bigDecimal2 = new BigDecimal("3.00");BigDecimal bigDecimal3 = bigDecimal1.divide(bigDecimal2, 2, RoundingMode.HALF_UP);System.out.println(bigDecimal3);
}

输出结果如下:

0.33

TIPS:

● RoundingMode.UP:向远离零的方向舍入
● RoundingMode.DOWN:向靠近零的方向舍入
● RoundingMode.CEILING:向正无穷方向舍入
● RoundingMode.FLOOR:向负无穷方向舍入
● RoundingMode.HALF_UP:四舍五入,如果舍弃部分大于等于 0.5
● RoundingMode.HALF_DOWN:四舍五入,如果舍弃部分大于 0.5
● RoundingMode.HALF_EVEN:银行家舍入法,遵循 IEEE 754 标准

总结:

在这里插入图片描述

● 尽量使用字符串而非浮点类型来构造 BigDecimal 对象,以避免浮点数转换精度问题。
● 如果无法避免使用浮点类型,则可使用 BigDecimal.valueOf 方法来构造初始化值,以确保精确表示。
● 比较两个 BigDecimal 值的大小时,使用 compareTo 方法。如果需要严格限制精度的比较,可以考虑使用 equals 方法。
● 在进行 BigDecimal 运算前,明确指定精度和舍入模式。使用 setScale 方法设置精度,使用 setRoundingMode 方法设置舍入模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/154841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE 材质,如何只取0~1之间的值,其余值抛弃

假如0~1,floor为0,abs为0,Saturate为0,1-x为1,很好 假如1~2,floor为1,abs为1,Saturate为1,1-x为0,很好 假如2~3,floor为2,abs为2&am…

软件测试/人工智能丨引领未来:软件测试中的人工智能

在数字化潮流的推动下,软件测试领域正在经历一场革命性的变革,而这场变革的关键推手正是人工智能(AI)。AI的引入不仅加速了测试过程,而且赋予了测试领域新的可能性,将我们带入了一个前所未有的未来。 智能…

【MySQL--->用户管理】

文章目录 [TOC](文章目录) 一、用户管理表二、基本操作三、用户权限分配给用户某个数据库中某个表的某个权限. grant 权限 on 库.表名 to 用户名主机名. ![在这里插入图片描述](https://img-blog.csdnimg.cn/fe8eb171ef9343c3a09bd64d4f0db5c1.png)分配给用户某个数据库中全部表…

13.Oracle通过JDBC连接Java

Oracle通过JDBC连接Java 一、什么是JDBC二、Oracle通过JDBC连接Java1、导入jar包1.1 下载jar包1.2 将jar包导入到java项目中1.3编译jar包 2、连接数据库2.1 编写jdbc工具类2.2 对数据进行基本操作 一、什么是JDBC JDBC(Java Database Connectivity)是Jav…

微波功率计/频率计-87234系列USB峰值/平均功率计

仪器仪表 苏州新利通 87234系列 USB峰值/平均功率计 频率范围覆盖:50MHz~67GHz 一款基于USB 2.0接口的二极管检波式宽带功率测量仪器 国产思仪功率计 01 产品综述 87234D/E/F/L USB峰值/平均功率计是一款基于USB 2.0接口的二极管检波式宽带功率测…

PowerShell无人参与安装最新版本SQL Server Management Studio (SSMS)

文章目录 下载SQL Server Management Studio (SSMS)Power Shell实现无人安装推荐阅读 下载SQL Server Management Studio (SSMS) SSMS 19.2 是最新的正式发布 (GA) 版本。 如果已经安装了 SSMS 19 预览版,需要在安装 SSMS 19.2 之前将其卸载。 如果安装了 SSMS 19.…

负载均衡Ribbon和Feign的使用与区别

Ribbon 的介绍 Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。主要功能是提供客户端的软件负载均衡和服务调用。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer…

JVM基础- 垃圾回收器

基本介绍 Java虚拟机(JVM)中的垃圾回收器是用来自动管理内存的关键组件。它负责识别并回收不再使用的内存,从而防止内存泄漏。不同的JVM实现提供了多种垃圾回收器,每种回收器都有其特定的使用场景和性能特点。以下是一些常见的JV…

【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏2(附项目源码)

文章目录 先看看最终效果前言生成走廊生成房间修复死胡同增加走廊宽度获取走廊位置信息集合方法一方法二 源码完结 先看看最终效果 前言 上期已经实现了房间的生成,本期紧跟着上期内容,生成走廊并结合上期内容生成连通的房间。 生成走廊 修改Procedur…

集成多元算法,打造高效字面文本相似度计算与匹配搜索解决方案,助力文本匹配冷启动[BM25、词向量、SimHash、Tfidf、SequenceMatcher]

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

zabbix的安装配置,邮件告警,钉钉告警

zabbix监控架构 zabbix优点 开源,无软件成本投入server对设备性能要求低支持设备多,自带多种监控模板支持分布式集中管理,有自动发现功能,可以实现自动化监控开放式接口,扩展性强,插件编写容易当监控的item…

力扣C++学习笔记——C++ 给vector去重

要使用std::set对std::vector进行去重操作,您可以将向量中的元素插入到集合中,因为std::set会自动去除重复元素。然后,您可以将集合中的元素重新存回向量中。以下是一个示例代码,演示如何使用std::set对std::vector进行去重&#…

Linux系统编程 系统编程概念

1.系统调用 系统调用(system call)其实是 Linux 内核提供给应用层的应用编程接口(API),是 Linux 应用层进入内核的入口。不止 Linux 系统,所有的操作系统都会向应用层提供系统调用,应用程序通过…

为什么Transformer模型中使用Layer Normalization(Layer Norm)而不是Batch Normalization(BN)

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…

GCC 学习

GCC Resource Center for GCC Internalshttps://www.cse.iitb.ac.in/grc/这是个不错资料网站,有兴趣的可以了解下

本地搭建Stackedit Markdown编辑器结合内网穿透实现远程访问

文章目录 1. docker部署Stackedit2. 本地访问3. Linux 安装cpolar4. 配置Stackedit公网访问地址5. 公网远程访问Stackedit6. 固定Stackedit公网地址 StackEdit是一个受欢迎的Markdown编辑器,在GitHub上拥有20.7k Star!,它支持将Markdown笔记保…

AR远程辅助技术应用到气象部门有何好处?

随着科技的不断发展,人类对于自然环境的理解和掌控能力也在不断提升。其中,AR(增强现实)技术的应用,为气象监控带来了革命性的变化。AR气象远程监控,就是将AR技术与气象监控相结合,通过虚拟与现实的融合,实…

Hive安装配置 - 本地模式

文章目录 一、Hive运行模式二、安装配置本地模式Hive(一)安装配置MySQL1、删除系统自带的MariaDB2、上传MySQL组件到虚拟机3、在主节点上安装MySQL组件4、在主节点上配置MySQL(1)查看MySQL服务状态(2)查看M…

蓝桥杯每日一题2023.11.18

题目描述 蓝桥杯大赛历届真题 - C 语言 B 组 - 蓝桥云课 (lanqiao.cn) 题目分析 本题使用搜索,将每一个格子进行初始赋值方便确定是否为相邻的数,将空出的两个格子首先当作已经填好数值为100,此时从第一个格子右边的格子开始搜索&#xff…