Java 之 BigDecimal 使用填坑
BigDecimal 的概述
Java 在 java.math 包中提供了 API 类 BigDecimal,用于对超过 16 位有效位的数进行精确的运算。双精度浮点型变量 double 可以处理 16 位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。在一般情况下,对于那些不需要准确计算精度的数字,可以直接使用 Float 和 Double 处理,但是 Double.valueOf(String) 和 Float.valueOf(String) 会丢失精度。所以在开发中,如果需要高精度的计算结果,则必须使用 BigDecimal 类来操作数据。BigDecimal 所创建的是对象,因此不能使用传统的 +、-、×、÷ 等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是 BigDecimal 的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
BigDecimal 的舍入模式
舍入模式(Rounding Mode)是 BigDecimal 类中的静态变量,其中包括了八种常见的舍入规则,比如四舍五入法、银行家舍入法等。
ROUND_UP:- 向远离零的方向舍入。舍弃非零部分,并将非零舍弃部分相邻的一位数字加一。
ROUND_DOWN:- 向接近零的方向舍入。舍弃非零部分,同时不会非零舍弃部分相邻的一位数字加一,采取截取行为。
ROUND_CEILING:- 向正无穷的方向舍入。如果为正数,舍入结果同 ROUND_UP 一致;如果为负数,舍入结果同 ROUND_DOWN 一致。
- 特别注意,此模式不会减少数值大小。
ROUND_FLOOR:- ` 向负无穷的方向舍入。如果为正数,舍入结果同 ROUND_DOWN 一致;如果为负数,舍入结果同 ROUND_UP 一致。
- 特别注意,此模式不会增加数值大小。
ROUND_HALF_UP:- 向 “最接近” 的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。
- 如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。
- 这种模式也就是常说的 “四舍五入”。
ROUND_HALF_DOWN:- 向 “最接近” 的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。
- 如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。
- 这种模式也就是常说的 “五舍六入”。
ROUND_HALF_EVEN:- 如果舍弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同。
- 如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
- 注意:在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。
- 此舍入模式也称为 “银行家舍入法”,主要在美国使用。
- 这种模式也就是常说的 “四舍六入五留双”。被舍位为 5 时有两种情况,如果前一位为奇数,则入位,否则舍去。
ROUND_UNNECESSARY:- 断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出 ArithmeticException。
BigDecimal 的注意事项
- 当使用
float、double这些浮点数据类型时,会丢失精度。 - 当 BigDecimal 使用除法时,商的结果需要指定舍入模式,否则会抛出
ArithmeticException异常。比如ROUND_HALF_UP模式是 “四舍五入”,ROUND_HALF_DOWN模式是 “五舍六入”。 - 禁止使用构造方法
BigDecimal(double)的方式将double值转换为 BigDecimal 对象,因为存在丢失精度的风险。推荐使用入参为 String 类型的构造方法,或者使用BigDecimal.valueOf()方法。 - 等值比较应该使用
compareTo()方法,而不是equals()方法。因为equals()方法会比较值和精度(1.0 与 1.00 比较的返回结果为true),而compareTo()方法则会忽略精度。
阿里巴巴 Java 开发手册
第一条开发准则

1 | public class FloatDemo { |
程序运行的输出结果:
1 | false |
第二条开发准则

1 | public class BigDecimalDemo { |
程序运行的输出结果:
1 | equals() 比较结果:false |
第三条开发准则

1 | public class BigDecimalDemo { |
程序运行的输出结果:
1 | amount1: 0.0299999999999999988897769753748434595763683319091796875 |
其他开发准则
科学计数法的使用
1 | public class BigDecimalDemo { |
程序运行的输出结果:
1 | 1.23456789012345677E+18 |
除法需要设置舍入模式
当 BigDecimal 使用除法时,商的结果需要指定舍入模式,否则会抛出 ArithmeticException 异常。比如 ROUND_HALF_UP 模式是 “四舍五入”,ROUND_HALF_DOWN 模式是 “五舍六入”。
1 | public class BigDecimalDemo { |
程序运行的输出结果:
1 | 0.67 |
如何选择数据库表字段的类型
在电商项目的支付 / 购物车模块中,往往会涉及到金额处理的操作,那么应该如何设计数据库表才能保证金额数据的精度呢?
- 数据库表字段的类型可以选择
DECIMAL(M, D),其中M是数字的最大位数,包括整数和小数部分,而D是小数点右边的小数位数。比如:DECIMAL(10, 2)可以存储最多 10 位数字,其中包括 2 位小数位,可以存储的范围是 -99999999.99 到 99999999.99。

- POJO 的属性可以使用
BigDecimal类型
1 |
|
浮点数比较值相等
在不使用 BigDecimal 的情况下,浮点数不能使用 == 或者 equals() 方法来进行等值判断(示例代码如下)。这是因为浮点数采用 “尾数 + 阶码” 的编码方式,类似于科学计数法的 “有效数字 + 指数” 的表示方式。二进制无法精确表示大部分的十进制小数,具体原理可参考《码出高效》。
1 | public class FloatDemo { |
程序运行的输出结果:
1 | false |
正确的写法应该是指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
1 | public class FloatDemo { |
程序运行的输出结果:
1 | true |
最佳编码实践
在电商项目的支付 / 购物车模块中,往往会涉及到金额处理的操作,那么可以直接使用下面的 BigDecimal 高精度数学运算工具类。
1 | import java.math.BigDecimal; |
