Java--BigDecimal 高精度计算

0 阅读4分钟

在 Java 开发中,float、double 浮点类型存在天然精度丢失问题,绝对无法用于金额、利息、汇率、精密计量等场景。Java 官方提供 BigDecimal 类,专门用于任意精度的十进制精确运算,是金融系统、支付项目、科学计算的核心必备类。

本文完整梳理 BigDecimal 的概述、核心优势、特性、对象创建、四则运算、舍入模式、比较规则、类型转换、高频踩坑点、实战规范,可直接用于开发落地与面试背诵。

一、BigDecimal 概述

BigDecimal 位于 java.math 包下,是 Java 专门用于高精度浮点数值运算的工具类。

主要应用场景:货币金额计算、金融利息结算、科学精密运算、工程测量、高精度统计等对数值精度要求极高的业务场景。

二、为什么需要 BigDecimal?(核心痛点)

float / double 采用 IEEE754 二进制浮点存储规范,无法精准表示所有十进制小数,会出现永久性精度丢失。

public class DoubleErrorTest {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2);
        // 输出结果:0.30000000000000004
    }
}

这种误差在金融场景是致命 Bug,而 BigDecimal 基于十进制字符串存储数值,从根源杜绝精度丢失。

三、BigDecimal 核心优势与特点

1. 任意精度存储

float/double 固定 32/64 位精度,而 BigDecimal 精度仅受限于当前设备内存,支持超大数、极小小数的超高精度运算。

2. 彻底避免精度丢失

不采用二进制近似存储,而是直接解析十进制数值,精准表示小数部分,无舍入误差。

3. 不可变类

BigDecimal 是不可变类,一旦对象创建完成,内部数值无法修改。

所有运算方法(加减乘除、保留精度)不会修改原对象,而是返回一个全新的 BigDecimal 对象。

💡 开发易错点:运算结果必须接收返回值,否则运算无效。

四、BigDecimal 对象创建(推荐与避坑)

BigDecimal 构造方式分推荐、不推荐两类,禁止使用 double 直接构造

1. 推荐写法(无精度丢失)

// 方式1:字符串构造(最安全、企业首选)
BigDecimal bd1 = new BigDecimal("3.14159");

// 方式2:valueOf 静态方法(底层封装字符串,安全)
BigDecimal bd2 = BigDecimal.valueOf(0.1);

2. 不推荐写法(严重精度问题)

// 禁止!直接传入 double 会保留原生浮点误差
BigDecimal bd3 = new BigDecimal(0.1);
System.out.println(bd3); 
// 输出:0.10000000000000000555...

原理:double 本身已经存在精度丢失,再封装为 BigDecimal 会将误差带入。

五、核心四则运算方法

所有运算必须接收返回值,原对象不改变。

1. 加法、减法、乘法、除法

BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");

// 加法
BigDecimal addRes = a.add(b);
// 减法
BigDecimal subRes = a.subtract(b);
// 乘法
BigDecimal mulRes = a.multiply(b);
// 除法(必须指定精度、舍入模式,必写!)
BigDecimal divRes = a.divide(b, 2, RoundingMode.HALF_UP);

2. 除法强制注意事项(高频报错)

BigDecimal 除法如果除不尽且不指定保留位数,直接抛出异常:ArithmeticException: Non-terminating decimal expansion

所以业务中 divide 方法必须传两个参数:保留位数 + 舍入模式

六、八大舍入模式详解

JDK1.5+ 推荐使用 RoundingMode 枚举(废弃旧的静态常量),核心常用 4 种:

1. RoundingMode.HALF_UP(四舍五入|最常用)

小数点后大于等于5进位,小于5舍去,金融、金额计算标准模式。

BigDecimal num1 = new BigDecimal("10");
BigDecimal num2 = new BigDecimal("3");
BigDecimal res = num1.divide(num2,2,RoundingMode.HALF_UP);
// 结果:3.33

2. RoundingMode.FLOOR(向下取整/向零舍入)

直接舍弃多余小数,不进位,数值向更小方向靠拢。

3. RoundingMode.CEILING(向上取整)

只要存在多余小数,直接进位,常用于费用向上取整场景。

4. RoundingMode.DOWN(直接截断)

无条件舍弃尾数,仅保留指定位数,无任何进位。

七、BigDecimal 等值比较(超级大坑)

1. 禁止使用 equals() 比较!

equals() 会同时比较数值 + 精度位数,会导致数值相等但精度不同时判定为不相等。

BigDecimal bd1 = new BigDecimal("10.5");
BigDecimal bd2 = new BigDecimal("10.500");
System.out.println(bd1.equals(bd2)); // false

2. 标准写法:使用 compareTo()(业务唯一推荐)

compareTo() 仅比较真实数值大小,忽略精度尾数 0。

  • 返回 0:两个数值相等
  • 返回 -1:当前值小于比较值
  • 返回 1:当前值大于比较值
int result = bd1.compareTo(bd2);
System.out.println(result); // 0 代表相等

八、BigDecimal 与基本类型转换

转换为整数类型会直接截断小数部分,不会四舍五入,需手动处理精度。

BigDecimal bd = new BigDecimal("123.456");

// 转int(舍弃小数)
int intVal = bd.intValue(); // 123

// 转long
long longVal = bd.longValue(); // 123

// 转double(慎用,会丢失高精度)
double doubleVal = bd.doubleValue(); // 123.456

九、高频开发规范与避坑总结

  • ✅ 创建对象优先使用 new BigDecimal("字符串")BigDecimal.valueOf()
  • ❌ 禁止使用 double 构造 BigDecimal
  • ✅ 所有运算必须接收返回值,不可原地修改
  • ✅ 除法必须指定保留位数与舍入模式
  • ✅ 等值比较只用 compareTo,禁用 equals
  • ✅ 金额计算统一使用 HALF_UP 四舍五入
  • ✅ 数据库金额字段对应 decimal 类型,不使用 double

十、总结

BigDecimal 是 Java 高精度运算的唯一解决方案,彻底解决 float/double 的浮点精度丢失问题,是金融、支付、精密计算项目的必备核心类。