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

embedded/2025/3/1 22:24:55/

本文已收录至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/embedded/169163.html

相关文章

SQL Server 链接服务器 MySQL 详细步骤

目录 前言 一、准备工作 1. 确认需求 2. 获取权限 二、安装必要的驱动程序和工具 1.下载并安装MySQL ODBC驱动&#xff1a; 2.安装 SQL Server 和 MySQL 的管理工具&#xff1a; 三、配置 SQL Server 以连接到MySQL 1.执行创建链接服务器的T-SQL语句&#xff1a; 2.配…

GD32F450 使用

GB32F450使用 1. 相关知识2. 烧写程序3. SPI3.1 spi基础3.2 spi代码 4. 串口4.1 串口引脚4.2 串口通信代码 问题记录1. 修改晶振频率 注意&#xff1a;GD32F450 总共有三种封装形式&#xff0c;本文所述的相关代码和知识&#xff0c;均为 GD32F450IX 系列。 1. 相关知识 参数配…

Linux(CentOS)安装 MySQL

CentOS版本&#xff1a;CentOS 7 三种安装方式&#xff1a; 一、通过 yum 安装&#xff0c;最简单&#xff0c;一键安装&#xff0c;全程无忧。 二、通过 rpm 包安装&#xff0c;需具备基础概念及常规操作。 三、通过 gz 包安装&#xff0c;需具备配置相关操作。 --------…

uniapp中使用leaferui使用Canvas绘制复杂异形表格的实现方法

需求&#xff1a; 如下图&#xff0c;要实现左图的样式&#xff0c;先实现框架&#xff0c;文字到时候 往里填就行了&#xff0c;原来的解决方案是想用css,html来实现&#xff0c;发现实现起来蛮麻烦的。我也没找到合适的实现方法&#xff0c;最后换使用canvas来实现&#xff…

linux第四讲----基础开发工具vim

1.软件安装 这里以ubuntu为例&#xff0c;安装sl软件,输入这个命令即可自动安装~ 使用一下&#xff0c;输入sl&#xff0c;屏幕上会出现一个移动的小火车 之后不想要了准备卸载就输入&#xff1a; 注意&#xff1a;1&#xff09;下载软件时也可以进行搜索~ 2&#xff09;cento…

ElasticSearch核心技术解析:倒排索引与IK分词器如何解决中文搜索痛点

在传统数据库中&#xff0c;模糊查询往往难以应对现实中的复杂情况。随着数据量的增加&#xff0c;查询时间显著变慢&#xff0c;功能也相对单一。当搜索时出现错别字或拼音输入时&#xff0c;搜索结果往往不尽如人意。因此&#xff0c;我们需要学习一种强大的搜索框架技术——…

C#开发——日期操作类DateTime

在C#中&#xff0c;日期和时间的操作主要通过 System.DateTime 类来实现。 DateTime 提供了丰富的属性和法&#xff0c;用于处理日期和时间的创建、格式化、比较和计算等操作。以下是一些常用的日期函数和特性&#xff1a; 一、创建日期和时间 1、直接指定日期和时间&…

TCP长连接与短连接

TCP长连接与短连接 TCP&#xff08;传输控制协议&#xff09;中的长连接和短连接是两种不同的连接管理方式&#xff0c;各有优缺点&#xff1a; 短连接 短连接是指客户端与服务器完成一次数据交换后就断开连接。下次需要通信时&#xff0c;再重新建立连接。 特点&#xff1…