Oracle NUMBER 型別精度高達 38 位,Java 基本型別 float (7 位有效數字)、double (15-16 位) 均無法無損映射。本文實測對比 getFloat/getDouble/getBigDecimal 行為,給出「查詢→計算→顯示」全鏈路最佳實踐,避免金額計算失真、報表對不上等生產事故。
-- Oracle 表
CREATE TABLE test_amt (id NUMBER, amt NUMBER(18,2));
INSERT INTO test_amt VALUES (1, 40000.12);
COMMIT;
// Java 代碼
ResultSet rs = stmt.executeQuery("SELECT amt FROM test_amt WHERE id=1");
rs.next();
// ❌ 錯誤:float 7位有效數字,40000.12 → 40000.119... → 顯示 40000.12 但實際已失真
float f = rs.getFloat(1);
System.out.println(f); // 40000.12 (看似正常,實則二進制近似值)
// ✅ 正確:double 15位,足夠容納 18 位精度的加減乘除中間結果
double d = rs.getDouble(1);
System.out.println(d); // 40000.12
// ✅ 最佳:BigDecimal 精確映射,零損失
BigDecimal bd = rs.getBigDecimal(1);
System.out.println(bd); // 40000.12
資料庫端格式化 (推薦,零網路傳輸格式化開銷)
SELECT TO_CHAR(amt, 'FM9999999999990.00') AS amt_fmt FROM test_amt;
-- FM 移除前導空格,.00 強制兩位小數
-- 結果: '40000.12' (VARCHAR2,前端直接用)
格式模板對照:
| 模板 | 含義 | 範例輸出 |
|---|---|---|
| 整數部最少1位、最多12位,小數固定2位 | 40000.12 | |
| 小數最少2位、最多4位 | 40000.12 / 40000.1234 | |
| 含千分位分隔符、小數2位 (依 NLS) | 40,000.12 |
// ❌ 錯誤:float 累加誤差放大
float sum = 0f;
while (rs.next()) sum += rs.getFloat(1);
// ❌ 風險:double 二進制浮點仍有 0.1 等無法精確表示
double sum = 0d;
while (rs.next()) sum += rs.getDouble(1);
BigDecimal bd = BigDecimal.valueOf(sum).setScale(2, RoundingMode.HALF_UP);
// ✅ 正確:BigDecimal 全程精確
BigDecimal sum = BigDecimal.ZERO;
while (rs.next()) {
sum = sum.add(rs.getBigDecimal(1)); // 直接取 BigDecimal
}
BigDecimal result = sum.setScale(2, RoundingMode.HALF_UP);
// 取兩欄 NUMBER 相加
BigDecimal val1 = rs.getBigDecimal(3); // 欄位 3
BigDecimal val2 = rs.getBigDecimal(4); // 欄位 4
BigDecimal total = val1.add(val2).setScale(2, RoundingMode.HALF_UP);
bean.setTotal(total.toPlainString()); // toPlainString 避免科學記號
ojdbc8.jar (JDK 8+):getBigDecimal(int) 回傳 java.math.BigDecimal,精度完整保留。ojdbc6:行為一致,但建議升級。rs.getObject(int) 接收 NUMBER,可能回傳 BigDecimal、Long、Double 混雜,導致 ClassCastException。-- 建表時限制精度
CREATE TABLE finance (
id NUMBER PRIMARY KEY,
amt NUMBER(18,2) NOT NULL -- 總位18、小數2,超拋異常
);
-- 建立格式化視圖供報表直連
CREATE VIEW v_finance_fmt AS
SELECT id, TO_CHAR(amt, 'FM9999999999990.00') AS amt_fmt
FROM finance;
getBigDecimal(),嚴禁 getFloat()/getDouble()。BigDecimal,add/subtract/multiply/divide 皆為精確運算。setScale(2, RoundingMode.HALF_UP)。TO_CHAR(..., 'FM...0.00') 推給前端字串。NUMBER(p,s) 讓資料庫層面攔截超精度寫入。貫徹「資料庫存精度、Java 算精度、顯示才格式化」三層原則,即可從根源杜絕 Oracle NUMBER 精度遺失問題,守住金融、計費、庫存等核心業務資料正確性。