🏆本文收录于「滚雪球学SpringBoot」(全网一个名)专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
🌟 前言:开发与精度的爱恨情仇
哈咯啊,同学们!今天我要分享一个比较有趣的知识点,恐怕大家基本都回答的不够全面,这也是前天我经理突然对我灵魂来上的重重一击,还好我底子厚,经得住考验,如果换做是你,你能如何满分回答领导提出的拷问?
或许你们曾经也有过因为浮点数的精度问题抓狂?比如说,当你愉快地写下 0.1 + 0.2
时,满心期待的结果为 0.3
,结果程序却冷漠地给你输出 0.30000000000000004
,直接给你的程序人生当头一棒!😤,这答案连我不懂算数的太奶看了都能直接给晕过去!
类似的问题在计算机中屡见不鲜,尤其是在金融计算、科学计算等领域,如果计算结果稍有偏差,可能就会引发无法挽回的后果。于是,在Java 语言 中,噔噔噔 --- BigDecimal,由此横空出世,它不但精准得让人放心,还能让你远离那些"不靠谱"的浮点数。
今天这篇文章,bug菌我的目的就是带着大家能够全面了解 BigDecimal 的魔法 !既有专业分析,也有生动案例,帮你一文搞懂:为什么 BigDecimal 可以不丢失精度?并且如何用好它?它到底有多强大?,让你秒上手,就是我今天写此文的最终目的,而且让你无论过去多久,依旧能够回忆起,它 - BigDecimal 为什么不会丢失精度?
🧐 浮点数为什么会丢失精度?
想必这个问题,浮点数的精度问题是每个开发者都无法绕过的坑。在搞懂 BigDecimal 之前,我们必须先弄清楚一个问题:浮点数到底为什么会丢失精度?
💻 浮点数的存储原理
在计算机中,浮点数(float
和 double
)的存储基于 IEEE 754 标准。它将一个数字拆分成三个部分存储:
- 符号位:表示正负。
- 指数:控制数值的范围。
- 尾数:保存有效数字。
用通俗点的话来说,浮点数在计算机中是用 二进制科学计数法 表示的,比如:
json
0.1 = 1.10011001100110011...(无限循环) × 2^-4
但是,由于计算机存储空间有限,无法表示无限循环小数,因此只能取一个近似值。这就导致:
json
0.1 在计算机中实际存储的并不是精确的 0.1,而是一个接近 0.1 的值。
当你进行类似 0.1 + 0.2
的操作时,这些近似值累积起来,就会产生微小误差,比如:
json
0.1 + 0.2 = 0.30000000000000004

🎩 BigDecimal 的魔法:为什么它能拯救我们?
BigDecimal 的核心优势在于它使用 字符串存储数字 ,而不是像浮点数那样用二进制表示。它的设计使得它可以避免浮点数的精度丢失问题。那么它到底是怎么做到的?让我们通过 BigDecimal 的源码 来一探究竟!🚀
🔍 从构造方法看 BigDecimal 的存储机制
📜 构造方法源码
以下是 BigDecimal
的部分构造方法的源码:
java
// 基于字符串的构造方法
public BigDecimal(String val) {
// 校验输入的字符串是否为有效数字
this(new BigDecimalParser(val).bigInteger, BigDecimalParser.scale, 0);
}
// 基于 double 的构造方法
public BigDecimal(double val) {
this(Double.toString(val));
}
从源码中可以看出,BigDecimal 最推荐的构造方式是通过字符串 ,因为字符串可以确保输入的数值精确无误。相比之下,如果你用 double
初始化 BigDecimal,其实底层仍然会把 double
转换为字符串(通过 Double.toString()
),再进一步存储。
这就是为什么我们总是强调:初始化 BigDecimal 时,最好用字符串而不是浮点数,因为直接传递浮点数容易把精度问题带入 BigDecimal。
🔧 BigDecimal 的内部存储结构
深入到 BigDecimal 的底层,我们会发现它的核心存储结构主要有两个部分:
- BigInteger:存储整数部分。
- int scale:存储小数点后的位数(也就是精度)。
下面是 BigDecimal 的重要字段源码:
java
// BigDecimal 核心字段
private final BigInteger intVal; // 用于存储大整数的核心字段
private final int scale; // 精度:小数点后位数
private transient int precision; // 用于缓存精度的字段
具体来看:
intVal
使用 BigInteger 来存储数字,意味着它可以支持任意大小的整数。scale
用来表示小数点后精确的位数。例如new BigDecimal("1.23")
的scale
为 2。precision
是一个临时缓存字段,用来加快计算速度。
🎯 示例分析
java
BigDecimal bigDecimal = new BigDecimal("123.456");
System.out.println(bigDecimal.unscaledValue()); // 输出:123456
System.out.println(bigDecimal.scale()); // 输出:3
在这个例子中:
intVal
实际存储的是 123456,即原数字去掉小数点后的整数值。scale
表示小数点后有 3 位。
示例运行结果展示如下:
通过这种设计,BigDecimal 能够准确地存储任意大小和任意精度的数字,而不会出现浮点数的近似值问题。
🧮 源码示例:加法运算
BigDecimal加法源码展示
以下是 BigDecimal
加法的核心源码:
java
public BigDecimal add(BigDecimal augend) {
// 如果两个数的 scale 相同,直接相加
if (scale == augend.scale) {
return new BigDecimal(intVal.add(augend.intVal), scale);
}
// 如果 scale 不同,调整 scale 后再相加
BigInteger scaledIntVal = this.intVal.multiply(BigInteger.TEN.pow(augend.scale - this.scale));
BigInteger result = scaledIntVal.add(augend.intVal);
return new BigDecimal(result, Math.max(this.scale, augend.scale));
}

源码解析
如下是对上的完整源码解析,希望能够帮到大家加深对BigDecimal 理解。
java
public BigDecimal add(BigDecimal augend) {
// 如果两个数的 scale 相同,直接相加
if (scale == augend.scale) {
return new BigDecimal(intVal.add(augend.intVal), scale);
}
// 如果 scale 不同,调整 scale 后再相加
BigInteger scaledIntVal = this.intVal.multiply(BigInteger.TEN.pow(augend.scale - this.scale));
BigInteger result = scaledIntVal.add(augend.intVal);
return new BigDecimal(result, Math.max(this.scale, augend.scale));
}
1. 方法签名
- 方法名 :
add
这是BigDecimal
类的一个方法,用于实现两个BigDecimal
对象的加法运算。 - 参数 :
BigDecimal augend
这是加法运算中的被加数(augend)。BigDecimal
类型的参数表示参与加法运算的另一个数。 - 返回值 :
BigDecimal
返回一个新的BigDecimal
对象,表示两个数的和。
2. 核心逻辑
2.1 检查小数点位数(Scale)
java
if (scale == augend.scale) {
return new BigDecimal(intVal.add(augend.intVal), scale);
}
scale
:BigDecimal
中的scale
表示小数点后的位数。例如,123.45
的scale
是 2,123.456
的scale
是 3。- 逻辑 :如果两个
BigDecimal
对象的小数点位数(scale
)相同,可以直接对它们的整数部分(intVal
)进行加法运算。intVal
:BigDecimal
内部使用BigInteger
来存储数值的整数部分。intVal
表示去掉小数点后的整数部分。- 操作 :调用
intVal.add(augend.intVal)
,将两个BigInteger
直接相加。 - 返回结果 :构造一个新的
BigDecimal
对象,其整数部分是加法结果,小数点位数(scale
)保持不变。
示例:
假设 this = 123.45
和 augend = 678.90
,它们的 scale
都是 2。
intVal
分别是12345
和67890
。- 直接相加:
12345 + 67890 = 80235
。 - 返回结果:
new BigDecimal(80235, 2)
,即802.35
。

2.2 处理不同小数点位数(Scale)
java
BigInteger scaledIntVal = this.intVal.multiply(BigInteger.TEN.pow(augend.scale - this.scale));
BigInteger result = scaledIntVal.add(augend.intVal);
return new BigDecimal(result, Math.max(this.scale, augend.scale));
- 逻辑 :如果两个
BigDecimal
对象的小数点位数(scale
)不同,需要先将它们调整到相同的小数点位数,再进行加法运算。- 调整小数点位数 :
augend.scale - this.scale
:计算两个数的小数点位数差。BigInteger.TEN.pow(augend.scale - this.scale)
:生成一个 10 的幂次方,用于调整小数点位数。例如,如果augend.scale - this.scale = 2
,则生成100
。this.intVal.multiply(...)
:将当前对象的整数部分乘以 10 的幂次方,调整小数点位数,使其与被加数的scale
一致。
- 加法运算 :
scaledIntVal.add(augend.intVal)
:将调整后的整数部分与被加数的整数部分相加。
- 构造结果 :
Math.max(this.scale, augend.scale)
:选择较大的scale
作为结果的小数点位数。new BigDecimal(result, ...)
:构造一个新的BigDecimal
对象,其整数部分是加法结果,小数点位数为较大的scale
。
- 调整小数点位数 :
示例:
假设 this = 123.45
(scale = 2
)和 augend = 67.890
(scale = 3
)。
intVal
分别是12345
和67890
。- 调整小数点位数:
augend.scale - this.scale = 3 - 2 = 1
10.pow(1) = 10
this.intVal.multiply(10) = 12345 * 10 = 123450
- 加法运算:
123450 + 67890 = 191340
- 构造结果:
Math.max(this.scale, augend.scale) = Math.max(2, 3) = 3
- 返回结果:
new BigDecimal(191340, 3)
,即191.340
。
3. 小结
如上源码的核心逻辑是实现两个 BigDecimal
对象的加法运算。它通过以下步骤确保加法的正确性:
- 检查小数点位数(
scale
) :- 如果两个数的
scale
相同,直接对整数部分进行加法运算。
- 如果两个数的
- 调整小数点位数 :
- 如果
scale
不同,将较小scale
的数调整到较大的scale
,再进行加法运算。
- 如果
- 构造结果 :
- 使用较大的
scale
构造新的BigDecimal
对象。
- 使用较大的
这种方法确保了加法运算的精确性,同时避免了因小数点位数不同导致的计算错误。
4. 设计意图
- 精确性 :
BigDecimal
用于处理高精度的数值运算,特别是在金融和科学计算中。通过精确调整小数点位数,确保加法运算的结果精确无误。 - 效率 :在
scale
相同时直接相加,避免了不必要的调整操作,提高了性能。 - 通用性:能够处理任意小数点位数的加法运算,适用于各种场景。
希望这段解析对同学们理解 BigDecimal.add
方法有帮助!如果还有其他问题,欢迎继续提问。
🎯 示例分析
java
BigDecimal num1 = new BigDecimal("1.23");
BigDecimal num2 = new BigDecimal("4.567");
BigDecimal result = num1.add(num2);
System.out.println(result); // 输出:5.797
示例运行结果展示如下:

🛠️ BigDecimal 的用法全面讲解
接下来,我们通过一个个具体案例来看看如何正确使用 BigDecimal。快来打开你的 IDE,跟着我一起代码实战一起学吧!
🎬 基本操作:加减乘除
加减乘除案例代码
java
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author bug菌
* @Source 公众号:猿圈奇妙屋
*/
public class OdMain {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("0.1");
BigDecimal num2 = new BigDecimal("0.2");
// 加法
BigDecimal sum = num1.add(num2);
System.out.println("加法结果:" + sum); // 输出:0.3
// 减法
BigDecimal diff = num1.subtract(num2);
System.out.println("减法结果:" + diff); // 输出:-0.1
// 乘法
BigDecimal product = num1.multiply(num2);
System.out.println("乘法结果:" + product); // 输出:0.02
// 除法
BigDecimal quotient = num1.divide(num2, 2, RoundingMode.HALF_UP);
System.out.println("除法结果:" + quotient); // 输出:0.50
}
}
案例执行结果
根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。实际运行结果展示如下:

用例代码分析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
如上这段代码演示了 BigDecimal
类在Java中进行高精度算术运算的能力,包括加法、减法、乘法和除法。BigDecimal
是一个用于处理精确浮点数运算的类,特别适用于需要高精度计算的场景,如金融和科学计算。
1. 创建 BigDecimal
对象
程序首先创建了两个 BigDecimal
对象:
num1
表示数值0.1
。num2
表示数值0.2
。
这里使用字符串构造 BigDecimal
对象的原因是为了避免浮点数表示不准确的问题。例如,直接使用 new BigDecimal(0.1)
可能会导致精度问题,因为 0.1
在浮点数表示中是不精确的。而使用字符串构造可以确保数值的精确表示。
2. 加法运算
程序执行了加法运算:
- 将
num1
和num2
相加,即0.1 + 0.2
。 - 结果是
0.3
,并打印出来。
BigDecimal
的 add
方法用于执行加法运算,它会精确地计算两个数的和,避免了浮点数运算中的精度问题。
3. 减法运算
程序执行了减法运算:
- 将
num1
减去num2
,即0.1 - 0.2
。 - 结果是
-0.1
,并打印出来。
BigDecimal
的 subtract
方法用于执行减法运算,同样确保了结果的精确性。
4. 乘法运算
程序执行了乘法运算:
- 将
num1
乘以num2
,即0.1 * 0.2
。 - 结果是
0.02
,并打印出来。
BigDecimal
的 multiply
方法用于执行乘法运算,确保了乘法结果的精确性。
5. 除法运算
程序执行了除法运算:
- 将
num1
除以num2
,即0.1 / 0.2
。 - 结果保留两位小数,并使用四舍五入的舍入模式。
- 最终结果是
0.50
,并打印出来。
BigDecimal
的 divide
方法用于执行除法运算。它需要指定结果的小数点位数(scale
)和舍入模式(RoundingMode
)。在这个例子中,结果保留两位小数,并使用 RoundingMode.HALF_UP
(四舍五入)作为舍入模式。
关键点
-
精度问题:
BigDecimal
用于处理高精度的浮点数运算,避免了float
和double
类型的精度问题。- 使用字符串构造
BigDecimal
对象可以确保数值的精确表示。
-
舍入模式:
- 在除法运算中,需要指定舍入模式,因为除法可能会产生无限循环的小数。
RoundingMode.HALF_UP
表示四舍五入,是常用的舍入模式之一。
-
方法功能:
add
:执行加法运算。subtract
:执行减法运算。multiply
:执行乘法运算。divide
:执行除法运算,需要指定结果的小数点位数和舍入模式。
小结
如上代码通过 BigDecimal
类展示了如何在Java中进行高精度的算术运算。它涵盖了加法、减法、乘法和除法的基本操作,并通过指定舍入模式确保了除法运算的精确性。程序逻辑清晰,适合初学者学习 BigDecimal
的基本用法。
🧮 舍入模式
BigDecimal 提供了 8 种舍入模式,以下是常用的几个:
- RoundingMode.HALF_UP:四舍五入。
- RoundingMode.HALF_DOWN:五舍六入。
- RoundingMode.CEILING:向上舍入。
- RoundingMode.FLOOR:向下舍入。
java
BigDecimal number = new BigDecimal("10.125");
BigDecimal rounded = number.setScale(2, RoundingMode.HALF_UP);
System.out.println("四舍五入结果:" + rounded); // 输出:10.13
根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

📊 比较大小
java
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("4.56");
int result = a.compareTo(b);
System.out.println(result); // 输出:-1 表示 a < b
根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

💡 实战演示:BigDecimal 在日常开发中的妙用
🎯 场景 1:货币计算
货币计算是 BigDecimal 最经典的应用场景,比如计算商品价格总额:
java
BigDecimal price = new BigDecimal("19.99");
BigDecimal quantity = new BigDecimal("3");
BigDecimal total = price.multiply(quantity).setScale(2, RoundingMode.HALF_UP);
System.out.println("总价:" + total); // 输出:59.97
如下是实际案例运行结果展示:

🎯 场景 2:科学计算
科学计算中,我们需要对精度要求极高的数值进行运算:
java
BigDecimal base = new BigDecimal("2");
BigDecimal exponent = new BigDecimal("10");
BigDecimal result = base.pow(exponent.intValue());
System.out.println("2 的 10 次方:" + result); // 输出:1024
如下是实际案例运行结果展示:

🎯 场景 3:统计分析
在统计数据时,BigDecimal 可以避免累计误差:
java
BigDecimal[] numbers = {
new BigDecimal("1.23"),
new BigDecimal("4.56"),
new BigDecimal("7.89")
};
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal num : numbers) {
sum = sum.add(num);
}
System.out.println("总和:" + sum); // 输出:13.68
如下是实际案例运行结果展示:

🤓 进阶拓展:BigDecimal 的高级用法
🔄 转换与输出
- 转字符串:
java
BigDecimal num = new BigDecimal("123.45");
String str = num.toString();
System.out.println(str); // 输出:123.45
- 转双精度:
java
double val = num.doubleValue();
System.out.println(val); // 输出:123.45
🧑🏭 自定义运算
java
BigDecimal num1 = new BigDecimal("10");
BigDecimal num2 = new BigDecimal("3");
BigDecimal result = num1.divide(num2, 5, RoundingMode.HALF_UP);
System.out.println("商:" + result); // 输出:3.33333
🎉 总结:为什么精度问题的解决之道是 BigDecimal?
总而言之,BigDecimal之所以能够解决精度问题,归纳有仨:
精确表示
BigDecimal
使用十进制表示法,能够精确表示任意精度的浮点数,避免了二进制表示带来的精度问题。- 例如,
0.1
在BigDecimal
中可以精确表示为0.1
,而不是近似值。
精确运算
BigDecimal
的算术运算方法(如add
、subtract
、multiply
和divide
)在执行时会考虑小数点位置(scale
),确保结果的精确性。- 例如,
0.1 + 0.2
的结果是0.3
,而不是0.30000000000000004
。
可配置的舍入模式
- 在需要舍入的情况下,
BigDecimal
提供了多种舍入模式,允许用户根据具体需求选择合适的舍入方式。 - 例如,在金融计算中,通常使用
RoundingMode.HALF_UP
(四舍五入)来确保结果的精确性。
简言之,BigDecimal
能够解决浮点数精度问题,原因在于其内部使用十进制表示法,能够精确表示任意精度的浮点数,并提供精确的算术运算方法。与 float
和 double
类型相比,BigDecimal
避免了二进制表示带来的精度问题,适用于需要高精度计算的场景。 虽然它在性能和操作简便性上稍逊于 double
,但在需要精度的地方,它绝对是你的得力助手。记住:当你需要靠谱的计算结果时,不要犹豫,直接选择 BigDecimal 吧!🎯
📣 关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主&最具价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。
-End-