必须用resultset.getbigdecimal()替代getdouble(),因oracle number是十进制高精度类型,double为二进制浮点(ieee 754),语义不兼容导致必然精度丢失;需判空、禁用new bigdecimal(double)、运算时指定scale与roundingmode。

直接用 ResultSet.getBigDecimal() 替代 getDouble() 或 getFloat(),这是最根本、最可靠的解法。不是“推荐”,而是必须——Oracle 的 NUMBER 是十进制高精度类型,double 是二进制浮点,语义不兼容,丢精度是必然的,不是偶然。
为什么 getDouble() 一定会丢精度
Oracle 的 NUMBER(10,2) 存的是精确的十进制值,比如 0.1 或 9999999999999999.99;而 double 按 IEEE 754 存储,它根本无法精确表示大多数十进制小数。0.1 在 double 里实际是 0.10000000000000000555...;超长整数(如 19 位 ID)超出 2^53 范围后,末尾数字直接归零。这不是 JDBC 驱动 bug,是底层数据模型冲突。
常见错误现象包括:
-
rs.getDouble("amount")返回12345678901234568.0,而数据库里明明存的是12345678901234567.99 - 金额计算前后对不上,尤其在累加、四舍五入后差异肉眼可见
- 前端展示 ID 字段变成
12345678901234568(最后一位被改写)
getBigDecimal() 怎么用才安全
Oracle JDBC 驱动默认将 NUMBER 映射为 BigDecimal,只要你不主动调用 getDouble(),这个保护就生效。但要注意几个实操细节:
- 别写
rs.getDouble("price"),统一改成rs.getBigDecimal("price") - 字段可能为 NULL:先判空再操作,
BigDecimal price = rs.getBigDecimal("price"); if (price != null) { ... } - 绝对不要用
new BigDecimal(rs.getDouble("price"))—— 这是在已损坏的数据上再套一层壳,精度回不来了 - 如果下游接口强制要
double(比如老系统或某些数学库),只能显式调用bigDecimal.doubleValue(),并清楚这是有损转换,仅限非关键路径
BigDecimal 后续运算容易踩的坑
拿到 BigDecimal 只是第一步,后续计算若不注意,照样引入新误差:
- 构造时别用
double参数:new BigDecimal(0.1)实际传入的是0.10000000000000000555...;该用字符串:new BigDecimal("0.1") - 除法必须指定精度和舍入模式,否则抛
ArithmeticException:val1.divide(val2, 2, RoundingMode.HALF_UP) - 比较大小用
compareTo(),不用equals():new BigDecimal("1.0").equals(new BigDecimal("1"))返回false(因 scale 不同) - 数据库定义为
NUMBER(16,2),业务加减也应统一保留 2 位小数,避免中间结果多出无效精度干扰最终展示
真正麻烦的从来不是“怎么换方法”,而是整个链路——从 JDBC 获取、到内存运算、再到序列化传输——每一步都可能悄悄把精度吃掉。尤其当团队里有人还在用 getDouble() + setScale() 自欺欺人时,问题会藏得更深。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











