トップ回答者
データグリッドビューで小数表示がおかしい

質問
-
OracleのNUMBER型(8,6) NOT NULL で作成したデータをデータグリッドビューで表示させようとすると
何行か毎に変な表示になります。
実際のデータは下記のとおりです。
データは0.1619のはずですが、データグリッド上は、0.16190000000000002
となります。
Dim IDX As Integer
Dim IDX2 As IntegerDataGridView1.DataSource = ""
DSET.Clear()
strSQL = ""
strSQL = strSQL & "SELECT * FROM HAIFURITSU WHERE KUBUN='" & Me.txt_Kubun.Text & "'"
DADP = New OracleDataAdapter(strSQL, CN)DADP.Fill(DSET, "HaifuRitsu")
'データをセット
DataGridView1.DataSource = DSET.Tables("HaifuRitsu")コードはこれだけしか書いてません。
よろしくお願いいたします。
回答
-
qoo_man さま よろしく。
DB 上の NUMERIC が可変長(精度・桁数指定) 、VB 上の Double が固定長(8 Byte)と双方の誤差が出ているためだと思います。
( Decimal でなくて Double ですか。)
DB 上の NUMERIC を Double に変えるか、
DataGridView で System.Math.Round(Double, Integer) As Double を使った別の列を作成するか。
この場合、編集しようとすると、DB 上に書き戻す前に 列のコピーも必要ですね。もう、ひとつ、 fill の時に整形してしまう方法もありますね。
つまり、SELECT で RITSU 列を数値演算してしまう。 ex 10000倍して、整数化して、10000で割るOracle には詳しくないので、組込関数を調べてください。
- 編集済み ShiroYuki_Mot 2012年8月28日 15:34 Fill 時の演算を追加
- 回答としてマーク qoo_man 2012年8月29日 9:40
-
-
OracleのNUMBER型は固定小数(10進の小数)ですが、Doubleは浮動小数(2進の小数)なので、変換誤差がでたのですね。
DataAdapterを作ったときに希望通りの型にならないことは、時々あります。
対策としては、以下の方法は如何でしょう?
○ 変換誤差をなくす方向
1.DataAdapter ではなく DataReader を使って型指定して読む
2.クエリ上で型を明示的にする
3.DataAdapterを弄る
○ 誤差は、気にしない 又は 気にしなくて良いようにする
4.DataGridViewのColumnのDefaultCellStyle.Formatを指定("##.0%"など)する
5.突っ込まれたときの言い訳を用意する
蛇足ですが、配布率って言葉はあまり使わないので、配賦率じゃないでしょうか?
配賦:予算や原価などを割り振る
配布:おおやけに配ること -
qoo_man さま ご質問にお答えします。
VB のコンパイラーが DB からデータを持ってくる際に、Double の精度で変換すれば、よいと認識している訳です。
勿論、DB 上は指定精度で値が保障されればよい訳で、精度外の下桁にごみが入っていても、しょうがない ... てことになります。DB 上の設定を変えない以上、DB と VB で扱う数値に誤差が生じるのは避けられないため、色々なプログラミング上の手段を講じて、ご自身で対応するしかありません。
数値変換関数 System.Math.Round や CINT CDOUBLE CDICIMAL を使って、小数点以下4桁に揃えるしかないと思います。
前投稿に書いた 10000倍して、整数化して、10000で割る で実現できませんか?アプリとして完成するまでは、つまり、Debug 時には、Table の列として、生の
RITSU 列(誤差有)と加工した RITU_2 列 を両方表示させたほうが良いと思います。
加工は RITU_2 に行い、保存する前に RITU_2 を RITU にコピーすると、確認が出来てよいかも。常に、誤差があることを前提に、Fill から Update まで、全ての工程で留意しないと、思わぬ Bug を引き起こしますので、注意してください。
PS:定かではありませんが、一度、 DB 上の RITU フィールドに対して、Update All で 上記と同じ演算を実施して見るのも手かも。
上手く行けば、下桁のごみが取れるかも。
BackUp した上で試して見て下さい。
演算に使える組込関数は Oracle で調べて下さい。- 編集済み ShiroYuki_Mot 2012年8月29日 3:23 誤字訂正
- 回答としてマーク qoo_man 2012年8月29日 9:40
-
外池です。
細かいですが・・・、「小数点以下4桁に揃える」操作をした結果をDouble型に入れると、やはりゴミの誤差が入ります。例えば、Double型は「1/16 = 0.0625」を正確に格納しますが、「1/16 + 0.0001 = 0.0626」は正確に格納できずゴミの誤差がつきます。
ゴミの誤差が表示に際して目に見える形で現れるかどうかは、また、別問題なので、ヤヤコシイですが。
私はDBのデータ型に詳しくないので、浮動小数点数の誤差の一般論だけを申し上げており、そんな私なら、最短距離の処置として、表示の際に丸めるhihijijiさんの方法を採ると思います。
-
OracleのほうをDoubleにするのではなく、VBのほうをDecimalに変えるには
どのようにすればいいでしょうか。
以下を読むとNumberはDecimalにマップされるようですが、私が違うところを見ていますかね?(Oracleにはあまり詳しくないものですから)
ODP.NET Types Overview
http://docs.oracle.com/cd/B28359_01/win.111/b28375/featTypes.htm#i1006604また、OracleDataAdapterのSafeMappingプロパティを使用して行う、Safe Type Mappingがあるようです。
OracleDataAdapter Safe Type Mapping
http://docs.oracle.com/cd/B28359_01/win.111/b28375/featSafeType.htm#CJABJECHなお、DataTableに一度Fillした後だと、カラムのDataTypeを変更することはできません。それでも行うであれば、以下が参考になるでしょう。
How To Change DataType of a DataColumn in a DataTable?
http://stackoverflow.com/questions/9028029/how-to-change-datatype-of-a-datacolumn-in-a-datatable★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
- 回答としてマーク qoo_man 2012年8月29日 9:40
すべての返信
-
hihijiji様
Debug.Write(DSET.Tables("HaifuRitsu").Columns.Item(3).DataType.ToString)で
出力したところ、System.Doubleとなっていました。
設定を変えた方がいいでしょうか?
DSET.Tables("HaifuRitsu").Columns("配布率").DataType = GetType(Decimal)
のようにしてみましたが、【~はコード -1073741819 (0xc0000005) で終了しました。】
となってうまくいきませんでした。
- 編集済み qoo_man 2012年8月28日 10:38
-
qoo_man さま よろしく。
DB 上の NUMERIC が可変長(精度・桁数指定) 、VB 上の Double が固定長(8 Byte)と双方の誤差が出ているためだと思います。
( Decimal でなくて Double ですか。)
DB 上の NUMERIC を Double に変えるか、
DataGridView で System.Math.Round(Double, Integer) As Double を使った別の列を作成するか。
この場合、編集しようとすると、DB 上に書き戻す前に 列のコピーも必要ですね。もう、ひとつ、 fill の時に整形してしまう方法もありますね。
つまり、SELECT で RITSU 列を数値演算してしまう。 ex 10000倍して、整数化して、10000で割るOracle には詳しくないので、組込関数を調べてください。
- 編集済み ShiroYuki_Mot 2012年8月28日 15:34 Fill 時の演算を追加
- 回答としてマーク qoo_man 2012年8月29日 9:40
-
-
OracleのNUMBER型は固定小数(10進の小数)ですが、Doubleは浮動小数(2進の小数)なので、変換誤差がでたのですね。
DataAdapterを作ったときに希望通りの型にならないことは、時々あります。
対策としては、以下の方法は如何でしょう?
○ 変換誤差をなくす方向
1.DataAdapter ではなく DataReader を使って型指定して読む
2.クエリ上で型を明示的にする
3.DataAdapterを弄る
○ 誤差は、気にしない 又は 気にしなくて良いようにする
4.DataGridViewのColumnのDefaultCellStyle.Formatを指定("##.0%"など)する
5.突っ込まれたときの言い訳を用意する
蛇足ですが、配布率って言葉はあまり使わないので、配賦率じゃないでしょうか?
配賦:予算や原価などを割り振る
配布:おおやけに配ること -
qoo_man さま ご質問にお答えします。
VB のコンパイラーが DB からデータを持ってくる際に、Double の精度で変換すれば、よいと認識している訳です。
勿論、DB 上は指定精度で値が保障されればよい訳で、精度外の下桁にごみが入っていても、しょうがない ... てことになります。DB 上の設定を変えない以上、DB と VB で扱う数値に誤差が生じるのは避けられないため、色々なプログラミング上の手段を講じて、ご自身で対応するしかありません。
数値変換関数 System.Math.Round や CINT CDOUBLE CDICIMAL を使って、小数点以下4桁に揃えるしかないと思います。
前投稿に書いた 10000倍して、整数化して、10000で割る で実現できませんか?アプリとして完成するまでは、つまり、Debug 時には、Table の列として、生の
RITSU 列(誤差有)と加工した RITU_2 列 を両方表示させたほうが良いと思います。
加工は RITU_2 に行い、保存する前に RITU_2 を RITU にコピーすると、確認が出来てよいかも。常に、誤差があることを前提に、Fill から Update まで、全ての工程で留意しないと、思わぬ Bug を引き起こしますので、注意してください。
PS:定かではありませんが、一度、 DB 上の RITU フィールドに対して、Update All で 上記と同じ演算を実施して見るのも手かも。
上手く行けば、下桁のごみが取れるかも。
BackUp した上で試して見て下さい。
演算に使える組込関数は Oracle で調べて下さい。- 編集済み ShiroYuki_Mot 2012年8月29日 3:23 誤字訂正
- 回答としてマーク qoo_man 2012年8月29日 9:40
-
外池です。
細かいですが・・・、「小数点以下4桁に揃える」操作をした結果をDouble型に入れると、やはりゴミの誤差が入ります。例えば、Double型は「1/16 = 0.0625」を正確に格納しますが、「1/16 + 0.0001 = 0.0626」は正確に格納できずゴミの誤差がつきます。
ゴミの誤差が表示に際して目に見える形で現れるかどうかは、また、別問題なので、ヤヤコシイですが。
私はDBのデータ型に詳しくないので、浮動小数点数の誤差の一般論だけを申し上げており、そんな私なら、最短距離の処置として、表示の際に丸めるhihijijiさんの方法を採ると思います。
-
外池 さま お世話になります。
こういう事だと思います。
DB 上では 0.1619 と表示される数を Fill すると、
VB の Double では 0.16190000012 と格納される。
これは、きっと、DB 上でもそのように格納されているのでしょう。
DB 上の有効桁は 8桁。この数値を numDB の Double 変数に置き換えれば、
numDB.ToString で 0.16190000012
(CInt(numDB * 10000) / 10000).ToString で 0.1619ですから、演算をしたら、上の式を必ず通すようにすれば、少数点以下4桁を守れるかと。
PS: qoo_man さまへ CInt でよいかどうかは、調べて下さい。 四捨五入のからみ。
追加
外池 さまや皆さんがお書きの様に、計算誤差の問題がありますので、
計算時に Decimal に変換して、 DB に戻す前に再度 Double に変換が良いと思います。
基本的には DB と VB の間は Number(8,6) と Double とで考えて下さい。- 編集済み ShiroYuki_Mot 2012年8月29日 8:38 計算誤差の件を追加
-
OracleのほうをDoubleにするのではなく、VBのほうをDecimalに変えるには
どのようにすればいいでしょうか。
以下を読むとNumberはDecimalにマップされるようですが、私が違うところを見ていますかね?(Oracleにはあまり詳しくないものですから)
ODP.NET Types Overview
http://docs.oracle.com/cd/B28359_01/win.111/b28375/featTypes.htm#i1006604また、OracleDataAdapterのSafeMappingプロパティを使用して行う、Safe Type Mappingがあるようです。
OracleDataAdapter Safe Type Mapping
http://docs.oracle.com/cd/B28359_01/win.111/b28375/featSafeType.htm#CJABJECHなお、DataTableに一度Fillした後だと、カラムのDataTypeを変更することはできません。それでも行うであれば、以下が参考になるでしょう。
How To Change DataType of a DataColumn in a DataTable?
http://stackoverflow.com/questions/9028029/how-to-change-datatype-of-a-datacolumn-in-a-datatable★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
- 回答としてマーク qoo_man 2012年8月29日 9:40
-
hihijiji様
SurferOnWww様
ShiroYuki_Mot様
外池様
trapemiya様
沢山のご意見、ご教授ありがとうございました。
まず、Datareaderでやってみましたが
Dim ORCLCMD As New OracleCommand(strSQL, CN)
CN.Open()
Dim ORCLRD As OracleDataReader
ORCLRD = ORCLCMD.ExecuteReader()
Dim Dt As New DataTableDt.Columns(0).DataType = GetType(Integer) ☆ここでエラーが発生しました。
Dt.Columns(1).DataType = GetType(Integer)
Dt.Columns(2).DataType = GetType(Integer)
Dt.Columns(3).DataType = GetType(Decimal)Dt.Load(ORCLRD)
DataGridView1.DataSource = Dt
ORCLRD.Close()
それで、OracleDataAdapter.ReturnProviderSpecificTypes をTRUEにすることにして
みました。DADP = New OracleDataAdapter(strSQL, CN)
DADP.ReturnProviderSpecificTypes = TrueDADP.Fill(DSET, "HaifuRitsu")
''データをセット
DataGridView1.DataSource = DSET.Tables("HaifuRitsu")0は消えてしまいましたが、誤差分が無くなりました。
色々とありがとうございました。
どれも、いいアドバイスだったので、失礼とは思いながら全て回答とさせていただきました。
- 編集済み qoo_man 2012年8月29日 9:43
-
> データの中は小数点以下4桁までしか入力されておらず、
> 計算結果を入れているわけではない為、誤差は考えにくいと
> 思っています。
コンピュータ内では 2 進数を使うため、ほとんどの 10
進少数
は正確に表現できないということは理解されているでしょうか?
「丸めの誤差」「浮動小数点」などをキーワードにググると参考
になるページが見つかると思います。例えば下記。
第4回 演算誤差の正体
http://pc.nikkeibp.co.jp/pc21/special/gosa/eg4.shtml
そんなことは言われなくても承知しているということでしたら失
礼しました。- 編集済み SurferOnWww 2012年8月29日 13:16 誤字訂正