這是同事發現的有趣現象(雖然她完全笑不出來)...

在ORACLE建了以下Table,並塞入一筆資料9.3到NUMBER(6,2)欄位中

CREATE TABLE PRECTEST (N NUMBER(6,2));
INSERT INTO PRECTEST VALUES (9.3);

然後用ODP.NET執行以下的程式

    string cmdStr = "SELECT N FROM PRECTEST";
    using (OracleConnection cn = new OracleConnection(cnStr))
    {
        cn.Open();
        OracleCommand cmd = new OracleCommand(cmdStr, cn);
        OracleDataReader dr = cmd.ExecuteReader();
        dr.Read();
        Console.WriteLine(Convert.ToDouble(dr["N"]).ToString());
        Console.WriteLine(Convert.ToDouble(dr["N"].ToString()).ToString());
        dr.Close();
        cn.Close();
    }

上述的兩行WriteLine,得到的結果不會相同,直接ToDouble會得到9.30000019073486,而ToString後再ToDouble才會得到9.3。

依我的認知,這個差異很像浮點數(Float、Double)所產生的近似誤差,這點之前的文章有提到過。

進一步挖掘,我發現更多有趣的事實:

  1. 同樣的Code,如果用的是System.Data.OracleClient,則不會產生上述的數字差異,所以這個問題只發生在ODP.NET上。
  2. 我檢測了dr.GetDataTypeName(0),System.Data.OracleClient時傳回NUMBER,而ODP.NET則傳回Single。也就是這個誤差源於Single轉Double的過程,Convert.ToDouble(9.3F)可以看到一模一樣的誤差結果。
  3. 我試著將NUMBER(6,2)改成NUMBER(14,2),則ODP.NET dr.GetDataTypeName(0)傳回Double,改成NUMBER(31,2)則傳回Decimal。由這個觀察,可以推論ODP.NET能"聰明地"視數字欄位長度決定自動選用不同的資料類別。
  4. 想要避免這個問題,最好在使用ODP.NET時,先確認數字欄位精確度再決定Convert.ToWhat,但此法在Schema變更時可能會出事。一律用ToDecimal應該是個辦法,再不然,先ToString()再轉應該也OK。

依一般人的直覺想法,Convert.ToDouble(Single),屬於低精確度轉高精確度,不該出現誤差,不過由以上實例來看,要留意此一浮點數特性。

PS: 我在MSDN Library Convert.ToDouble(Single)上加了一則Community Content,這是我第二次在MSDN上塗鴉(第一次在此)。


Comments

# by 大估

看到黑暗大的Oracle資料型別轉換問題,小弟在此提供另一個有關Oracle的Date型別toString()後的問題。 (Oracle的Date是包含時、分、秒) 程式如下: Dim strV1 as string="",strV2 as string="" Dim dtMember as Datatable = objSQL.GetInfoDT(strSQL) strV1=dtMember.rows(0).Item("BIRTHDAY") strV2=dtMember.rows(0).Item("BIRTHDAY").toString() strV1的值 2008/1/4 (主動將時、分、秒去掉) strV2的值 2008/1/4 12:00:00 (原汁原味) 兩者的值不同 備註:objSQL是小弟寫的DB元件,是使用System.Data.OracleClient

Post a comment