BigDecimal 为什么可以不丢失精度?

ops/2025/3/1 22:44:54/

本文已收录至Java面试网站:https://topjavaer.cn

大家好,今天咱们来聊聊 Java 中的 BigDecimal。在金融领域,数据的精确性相当重要,一个小数点的误差可能就意味着几百万甚至几千万的损失。而 BigDecimal 就是专门用来解决这种高精度计算问题的。今天,我就带大家深入了解一下,为什么 BigDecimal 能做到不丢失精度。


一、浮点数的“坑”:精度丢失

在 Java 中,我们通常用 floatdouble 来表示浮点数。但它们有一个致命的缺陷——精度丢失。比如,0.1 + 0.2 的结果并不是 0.3,而是 0.30000000000000004。这是因为在计算机内部,浮点数是用二进制表示的,而某些十进制小数无法精确地转换为二进制,从而导致了精度问题。

这种问题在金融领域是绝对不能容忍的。想象一下,银行账户余额显示为 999.999999999999,而不是 1000,用户会怎么想?所以,我们需要一种能够精确表示和计算小数的数据类型,这就是 BigDecimal 的用武之地。


二、BigDecimal 的“秘密武器”

BigDecimal 是 Java 中用来表示高精度小数的类,它内部使用了 BigInteger 来存储数值,并通过一个 scale 属性来记录小数点的位置。简单来说,BigDecimal 把一个小数拆成了两部分:整数部分和小数点的位置。

举个例子,2.36BigDecimal 中会被表示为:

  • 整数部分:236(用 BigInteger 存储)
  • 小数点位置:2(表示小数点后有两位)

这样一来,BigDecimal 就可以精确地表示任何小数,而不用担心精度丢失的问题。


三、BigDecimal 的加法运算

我们来看一个简单的例子,理解一下 BigDecimal 是如何进行加法运算的:

java">BigDecimal bigDecimal1 = BigDecimal.valueOf(2.36);
BigDecimal bigDecimal2 = BigDecimal.valueOf(3.5);
BigDecimal result = bigDecimal1.add(bigDecimal2);
System.out.println(result); // 输出:5.86

在这个例子中,bigDecimal1bigDecimal2 的小数位数不同(一个是两位小数,一个是两位小数)。BigDecimal 在进行加法运算时,会先将两个数的小数位数对齐,然后进行整数加法运算。

具体步骤如下:

  1. 对齐小数位数:将 3.5 转换为 3.50,这样两个数的小数位数就一致了。
  2. 整数加法:将 236350 相加,得到 586
  3. 设置小数点位置:根据小数位数(这里是两位),将结果表示为 5.86

这个过程的核心在于,BigDecimal 把小数运算转换为了整数运算,而整数运算是不会丢失精度的。


四、BigDecimal 的内部实现

BigDecimal 的内部实现非常精巧。它使用了 BigInteger 来存储整数部分,这样可以保证数值的范围几乎不受限制。同时,它通过 scale 属性来记录小数点的位置,从而实现了高精度的小数运算。

我们再来看一个稍微复杂一点的例子,理解一下 BigDecimal 是如何处理不同小数位数的加法运算的:

java">BigDecimal bigDecimal1 = new BigDecimal("2.36");
BigDecimal bigDecimal2 = new BigDecimal("3.5");
BigDecimal result = bigDecimal1.add(bigDecimal2);
System.out.println(result); // 输出:5.86

在这个例子中,bigDecimal1 的小数位数是两位,而 bigDecimal2 的小数位数是一位。BigDecimal 在进行加法运算时,会先将两个数的小数位数对齐,然后进行整数加法运算。

具体步骤如下:

  1. 对齐小数位数:将 3.5 转换为 3.50,这样两个数的小数位数就一致了。
  2. 整数加法:将 236350 相加,得到 586
  3. 设置小数点位置:根据小数位数(这里是两位),将结果表示为 5.86

这个过程的核心在于,BigDecimal 把小数运算转换为了整数运算,而整数运算是不会丢失精度的。

下面是add方法的源码实现:

java">/*** Returns a BigDecimal whose value is (this + augend), * and whose scale is max(this.scale(), augend.scale()).*/
public BigDecimal add(BigDecimal augend) {if (this.intCompact != INFLATED) {if ((augend.intCompact != INFLATED)) {return add(this.intCompact, this.scale, augend.intCompact, augend.scale);} else {return add(this.intCompact, this.scale, augend.intVal, augend.scale);}} else {if ((augend.intCompact != INFLATED)) {return add(augend.intCompact, augend.scale, this.intVal, this.scale);} else {return add(this.intVal, this.scale, augend.intVal, augend.scale);}}
}

进入第8行的add方法:

java">private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) {long sdiff = (long) scale1 - scale2;if (sdiff == 0) {return add(xs, ys, scale1);} else if (sdiff < 0) {int raise = checkScale(xs,-sdiff);long scaledX = longMultiplyPowerTen(xs, raise);if (scaledX != INFLATED) {return add(scaledX, ys, scale2);} else {BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys);return ((xs^ys)>=0) ? // same sign testnew BigDecimal(bigsum, INFLATED, scale2, 0): valueOf(bigsum, scale2, 0);}} else {int raise = checkScale(ys,sdiff);long scaledY = longMultiplyPowerTen(ys, raise);if (scaledY != INFLATED) {return add(xs, scaledY, scale1);} else {BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs);return ((xs^ys)>=0) ?new BigDecimal(bigsum, INFLATED, scale1, 0): valueOf(bigsum, scale1, 0);}}
}

这个例子中,该方法传入的参数分别是:xs=236,scale1=2,ys=35,scale2=1

该方法首先计算scale1 - scale2,根据差值走不同的计算逻辑,这里求出来是1,所以进入到最下面的else代码块(这块是关键):

  • 首先17行校验了一下数值范围
  • 18行将ys扩大了10的n次倍,这里n=raise=1,所以返回的scaledY=350
  • 接着就进入到20行的add方法:
java">private static BigDecimal add(long xs, long ys, int scale){long sum = add(xs, ys);if (sum!=INFLATED)return BigDecimal.valueOf(sum, scale);return new BigDecimal(BigInteger.valueOf(xs).add(ys), scale);
}

这个方法很简单,就是计算和,然后返回BigDecimal对象:


五、为什么 BigDecimal 不丢失精度?

现在我们已经明白了 BigDecimal 的基本原理,那么为什么它能够保证不丢失精度呢?原因就在于它把小数运算转换为了整数运算。整数运算是精确的,不会出现浮点数那种“四舍五入”的问题。

同时,BigDecimal 还提供了丰富的 API,支持各种数学运算,包括加法、减法、乘法、除法等。这些运算都基于整数运算,从而保证了精度。

举个例子,BigDecimal 的乘法运算会先将两个数的小数位数相加,然后进行整数乘法运算,最后根据总的小数位数设置小数点位置。这个过程同样保证了精度。


六、使用 BigDecimal 的注意事项

虽然 BigDecimal 是一个非常强大的工具,但在使用时也有一些需要注意的地方:

  1. 构造方法的选择:尽量使用字符串构造方法,而不是直接传入浮点数。因为浮点数本身就可能存在精度问题,而字符串构造方法可以精确地表示数值。

    java">BigDecimal bigDecimal = new BigDecimal("2.36"); // 推荐
    BigDecimal bigDecimal = BigDecimal.valueOf(2.36); // 不推荐
    
  2. 除法运算的精度BigDecimal 的除法运算可能会出现无限循环小数的情况,所以在进行除法运算时,需要指定精度和舍入模式。

    java">BigDecimal result = bigDecimal1.divide(bigDecimal2, 2, RoundingMode.HALF_UP);
    
  3. 性能问题:虽然 BigDecimal 能保证精度,但它的性能比 floatdouble 要差很多。所以在不需要高精度的场景下,尽量使用 floatdouble


七、BigDecimal 的实际应用

BigDecimal 在金融领域是不可或缺的工具。它虽然性能稍差,但精度极高,能够满足各种复杂的金融计算需求。比如,在银行系统中,账户余额、交易金额等都需要精确到小数点后两位,BigDecimal 是最佳选择。

此外,BigDecimal 还可以用于科学计算、大数据处理等场景。只要涉及到高精度的小数运算,BigDecimal 都能大显身手。


八、总结

今天,我们深入探讨了 BigDecimal 的原理和实现。通过把小数运算转换为整数运算,BigDecimal 能够精确地表示和计算小数,从而解决了浮点数精度丢失的问题。

在实际开发中,BigDecimal 是金融领域不可或缺的工具。它虽然性能稍差,但精度极高,能够满足各种复杂的金融计算需求。

最后,如果你觉得这篇文章对你有帮助,别忘了点赞和分享哦!

最后分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~

需要的小伙伴可以自行下载

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

围观朋友⭕:dabinjava


http://www.ppmy.cn/ops/162344.html

相关文章

docker启动elasticsearch,挂载文件报错:Device or resource busy

本人使用的是elasticsearch 8.17.0&#xff0c;不过通过看帖子发现这个问题很早之前就有了&#xff0c;并且到现在官方也没有很好的解决。 原始错误是在安全自动配置期间引发的&#xff0c;配置文件里显示的启用安全配置xpack.security.enabled: true可以跳过自动配置&#xf…

事故02分析报告:慢查询+逻辑耦合导致订单无法生成

一、事故背景与现象 时间范围 2022年2月3日 18:11~18:43&#xff08;历时32分钟&#xff09; 受影响系统 系统名称角色影响范围dc3订单数据库主库订单生成、事务回滚dc4订单数据库从库数据同步、容灾切换 业务影响 核心业务&#xff1a;手机点餐、C扫B支付订单无法推送至…

鸿蒙 ArkUI 实现敲木鱼小游戏

敲木鱼是一款具有禅意的趣味小游戏&#xff0c;本文将通过鸿蒙 ArkUI 框架的实现代码&#xff0c;逐步解析其核心技术点&#xff0c;包括动画驱动、状态管理、音效震动反馈等。 一、架构设计与工程搭建 1.1 项目结构解析 完整项目包含以下核心模块&#xff1a; ├── entry…

linux中安装部署Jenkins,成功构建springboot项目详细教程

参考别人配置Jenkins的git地址为https&#xff0c;无法连上github拉取项目&#xff0c;所以本章节介绍通过配置SSH地址来连github拉取项目 目录&#xff1a; 1、springboot项目 1.1 创建名为springcloudproject的springboot项目工程 1.2 已将工程上传到github中&#xff0c;g…

linux学习笔记2

认知权限信息 -或d或l:-表示文件,d表示文件夹,l表示软链接 rwx:r表示读权限,w表示写权限,x表示执行权限 文件类型所属用户权限所属用户组权限其它用户权限 修改权限chmod [-R] 权限 文件或文件夹 -R对文件夹内的全部内容应用同样的操作 chmod -R urwx,grx,ox 可使用简单表示r4…

【Prometheus】prometheus服务发现与relabel原理解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

Vue 3指令全解析:内置指令与自定义指令实战指南

Vue指令是模板语法的核心武器&#xff0c;它们以v-前缀的形式为HTML元素添加特殊功能。本文将深入探讨Vue 3中的指令系统&#xff0c;覆盖10个核心指令的妙用&#xff0c;并手把手教你打造专属自定义指令。 一、Vue指令基础认知 指令本质上是DOM操作的语法糖&#xff0c;它们&…

从 Spring Boot 2 升级到 Spring Boot 3 的终极指南

一、升级前的核心准备 1. JDK 版本升级 Spring Boot 3 强制要求 Java 17 及以上版本。若当前项目使用 Java 8 或 11&#xff0c;需按以下步骤操作&#xff1a; 安装 JDK 17&#xff1a;从 Oracle 或 OpenJDK 官网下载&#xff0c;配置环境变量&#xff08;如 JAVA_HOME&…