首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle NUMBER 型別精度遺失問題:Java 端 float/double/BigDecimal 正確處理指南

     編輯:Oracle 資料庫基礎

Oracle NUMBER 型別精度高達 38 位,Java 基本型別 float (7 位有效數字)、double (15-16 位) 均無法無損映射。本文實測對比 getFloat/getDouble/getBigDecimal 行為,給出「查詢→計算→顯示」全鏈路最佳實踐,避免金額計算失真、報表對不上等生產事故。

實測現象:float 精度災難

-- 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

三大場景處理策略

場景 1:單筆查詢直接顯示 (保留 2 位小數)

資料庫端格式化 (推薦,零網路傳輸格式化開銷)

SELECT TO_CHAR(amt, 'FM9999999999990.00') AS amt_fmt FROM test_amt;
-- FM 移除前導空格,.00 強制兩位小數
-- 結果: '40000.12' (VARCHAR2,前端直接用)

格式模板對照:

FM9999999999990.00FM9999999999990.0099FM999G999G999D00
模板含義範例輸出
整數部最少1位、最多12位,小數固定2位40000.12
小數最少2位、最多4位40000.12 / 40000.1234
含千分位分隔符、小數2位 (依 NLS)40,000.12

場景 2:Java 端累加後四捨五入 2 位

// ❌ 錯誤: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);

場景 3:多欄位相加後保留 2 位

// 取兩欄 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 避免科學記號

JDBC 驅動版本差異提示

  • 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;

總結清單

  1. 讀取 NUMBER 必用 getBigDecimal(),嚴禁 getFloat()/getDouble()。
  2. 計算全程用 BigDecimal,add/subtract/multiply/divide 皆為精確運算。
  3. 四捨五入只在最後輸出時 用 setScale(2, RoundingMode.HALF_UP)。
  4. 高頻顯示查詢 直接在 SQL 用 TO_CHAR(..., 'FM...0.00') 推給前端字串。
  5. 建表定義精度 NUMBER(p,s) 讓資料庫層面攔截超精度寫入。

貫徹「資料庫存精度、Java 算精度、顯示才格式化」三層原則,即可從根源杜絕 Oracle NUMBER 精度遺失問題,守住金融、計費、庫存等核心業務資料正確性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved