none
データグリッドビューで小数表示がおかしい RRS feed

  • 質問

  • OracleのNUMBER型(8,6) NOT NULL で作成したデータをデータグリッドビューで表示させようとすると

    何行か毎に変な表示になります。

    実際のデータは下記のとおりです。

    データは0.1619のはずですが、データグリッド上は、0.16190000000000002

    となります。

            Dim IDX As Integer
            Dim IDX2 As Integer

            DataGridView1.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")

    コードはこれだけしか書いてません。

    よろしくお願いいたします。

    2012年8月28日 6:10

回答

  • DSET.Tables("HaifuRitsu")のカラムRITSUのDataTypeが浮動小数になってたりしませんか?
    • 回答としてマーク qoo_man 2012年8月29日 9:39
    2012年8月28日 8:40
  • > 何行か毎に変な表示になります。

    単なる丸めの誤差の問題ということはないしょうか?

    違っていたら失礼しました。

    • 回答としてマーク qoo_man 2012年8月29日 9:43
    2012年8月28日 13:40
  • 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
    2012年8月28日 14:52
  • 外池と申します。

    非Double型(例えば、Decimal型やDB独特の小数点数型)からDouble型に変換する際に、浮動小数点数に特有の誤差が入り込みます。なので、Double型を経由しているのであれば、小さな誤差が見えてくることは不具合ではありません。

    これを避けるためには、

    Double型を経由し、適宜丸めて表示する、

    あるいは、

    Double型を経由せずに、表示する、
    (VBのウォッチ式でデータセット内の数値を見ているのは、この方法かな? 自身ナシ)

    どちらかと思います。

    • 回答としてマーク qoo_man 2012年8月29日 9:39
    2012年8月29日 2:05
  • OracleのNUMBER型は固定小数(10進の小数)ですが、Doubleは浮動小数(2進の小数)なので、変換誤差がでたのですね。
    DataAdapterを作ったときに希望通りの型にならないことは、時々あります。
    対策としては、以下の方法は如何でしょう?

    ○ 変換誤差をなくす方向
     1.DataAdapter ではなく DataReader を使って型指定して読む
     2.クエリ上で型を明示的にする
     3.DataAdapterを弄る

    ○ 誤差は、気にしない 又は 気にしなくて良いようにする
     4.DataGridViewのColumnのDefaultCellStyle.Formatを指定("##.0%"など)する
     5.突っ込まれたときの言い訳を用意する


    蛇足ですが、配布率って言葉はあまり使わないので、配賦率じゃないでしょうか?
    配賦:予算や原価などを割り振る
    配布:おおやけに配ること
    • 編集済み hihijiji 2012年8月29日 2:49
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 2:11
  • 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
    2012年8月29日 3:20
  • 外池です。

    細かいですが・・・、「小数点以下4桁に揃える」操作をした結果をDouble型に入れると、やはりゴミの誤差が入ります。例えば、Double型は「1/16 = 0.0625」を正確に格納しますが、「1/16 + 0.0001 = 0.0626」は正確に格納できずゴミの誤差がつきます。

    ゴミの誤差が表示に際して目に見える形で現れるかどうかは、また、別問題なので、ヤヤコシイですが。

    私はDBのデータ型に詳しくないので、浮動小数点数の誤差の一般論だけを申し上げており、そんな私なら、最短距離の処置として、表示の際に丸めるhihijijiさんの方法を採ると思います。


    • 編集済み 外池 2012年8月29日 4:08
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 4:02
  • 捕捉します。

    2. は strSQL の SELECT句 内で RITSUをもう一度Number型にキャストするなどして、コンパイラの動作が変わることを期待する
    3. は例えば OracleDataAdapter.ReturnProviderSpecificTypes プロパティを true にしたら OracleNumber構造体 で返してくれるような気がするのでそれを利用するとか ← 自信なし

    他にも方法はあると思いますが、思いついたのからあげました。
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 4:08
  • 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
    2012年8月29日 4:58
    モデレータ

すべての返信

  • DSET.Tables("HaifuRitsu")のカラムRITSUのDataTypeが浮動小数になってたりしませんか?
    • 回答としてマーク qoo_man 2012年8月29日 9:39
    2012年8月28日 8: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
    2012年8月28日 9:48
  • > 何行か毎に変な表示になります。

    単なる丸めの誤差の問題ということはないしょうか?

    違っていたら失礼しました。

    • 回答としてマーク qoo_man 2012年8月29日 9:43
    2012年8月28日 13:40
  • 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
    2012年8月28日 14:52
  • SurferOnWww様

    ありがとうございます。

    データの中は小数点以下4桁までしか入力されておらず、

    計算結果を入れているわけではない為、誤差は考えにくいと

    思っています。

    VBのウォッチ式でも確認しましたが、データセット内は小数点4桁だでしか

    データはありませんでした。

    2012年8月29日 1:44
  • ShiroYuki_Mot様

    ありがとうございます。

    OracleのほうをDoubleにするのではなく、VBのほうをDecimalに変えるには

    どのようにすればいいでしょうか。

    出来ればデータグリッドの内容をそのままDBに反映したいと考えています。

    2012年8月29日 1:48
  • 外池と申します。

    非Double型(例えば、Decimal型やDB独特の小数点数型)からDouble型に変換する際に、浮動小数点数に特有の誤差が入り込みます。なので、Double型を経由しているのであれば、小さな誤差が見えてくることは不具合ではありません。

    これを避けるためには、

    Double型を経由し、適宜丸めて表示する、

    あるいは、

    Double型を経由せずに、表示する、
    (VBのウォッチ式でデータセット内の数値を見ているのは、この方法かな? 自身ナシ)

    どちらかと思います。

    • 回答としてマーク qoo_man 2012年8月29日 9:39
    2012年8月29日 2:05
  • OracleのNUMBER型は固定小数(10進の小数)ですが、Doubleは浮動小数(2進の小数)なので、変換誤差がでたのですね。
    DataAdapterを作ったときに希望通りの型にならないことは、時々あります。
    対策としては、以下の方法は如何でしょう?

    ○ 変換誤差をなくす方向
     1.DataAdapter ではなく DataReader を使って型指定して読む
     2.クエリ上で型を明示的にする
     3.DataAdapterを弄る

    ○ 誤差は、気にしない 又は 気にしなくて良いようにする
     4.DataGridViewのColumnのDefaultCellStyle.Formatを指定("##.0%"など)する
     5.突っ込まれたときの言い訳を用意する


    蛇足ですが、配布率って言葉はあまり使わないので、配賦率じゃないでしょうか?
    配賦:予算や原価などを割り振る
    配布:おおやけに配ること
    • 編集済み hihijiji 2012年8月29日 2:49
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 2:11
  • 外池様

    ありがとうございます。

    変換の誤差ですか。

    説明を読んでいるとなるほどという感じです。

    Double型を経由しない方法がよく分かりません。

    よろしければご教授ください。


    • 編集済み qoo_man 2012年8月29日 3:13
    2012年8月29日 3:11
  • hihijiji 様

    ありがとうございます。

    配布率ではなく、配賦率ですね。恥ずかしい限りです。

    DataReaderを使う方法に変えてみようと思います。

    2と3はどうするのかよく分からないです。

    お手数でなければ、もう少しご教授ください。

    5の回答にhihijiji 様のユーモアを感じてしまいました。

    2012年8月29日 3:18
  • 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
    2012年8月29日 3:20
  • 外池です。

    細かいですが・・・、「小数点以下4桁に揃える」操作をした結果をDouble型に入れると、やはりゴミの誤差が入ります。例えば、Double型は「1/16 = 0.0625」を正確に格納しますが、「1/16 + 0.0001 = 0.0626」は正確に格納できずゴミの誤差がつきます。

    ゴミの誤差が表示に際して目に見える形で現れるかどうかは、また、別問題なので、ヤヤコシイですが。

    私はDBのデータ型に詳しくないので、浮動小数点数の誤差の一般論だけを申し上げており、そんな私なら、最短距離の処置として、表示の際に丸めるhihijijiさんの方法を採ると思います。


    • 編集済み 外池 2012年8月29日 4:08
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 4:02
  • 捕捉します。

    2. は strSQL の SELECT句 内で RITSUをもう一度Number型にキャストするなどして、コンパイラの動作が変わることを期待する
    3. は例えば OracleDataAdapter.ReturnProviderSpecificTypes プロパティを true にしたら OracleNumber構造体 で返してくれるような気がするのでそれを利用するとか ← 自信なし

    他にも方法はあると思いますが、思いついたのからあげました。
    • 回答としてマーク qoo_man 2012年8月29日 9:40
    2012年8月29日 4:08
  • ShiroYuki_Mot様

    詳しい内容ですね。ありがとうございます。

    一度誤差無しの表示と比較してみます。

    2012年8月29日 4:18
  • 外池様

    ありがとうございます。

    丸めるほうがいいのですね。

    そちらも試してみます。

    Double型の説明が分かりやすくて、理解しやすいです。

    2012年8月29日 4:22
  • hihijiji様

    こちらも、丁寧な説明でありがとうございます。

    奥が深いと実感しました。

    まずは先ほどの、Readerを試してみます。

    2012年8月29日 4:24
  • 外池 さま お世話になります。

    こういう事だと思います。
    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 計算誤差の件を追加
    2012年8月29日 4:55
  • 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
    2012年8月29日 4:58
    モデレータ
  • 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 DataTable

                Dt.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 = True

            DADP.Fill(DSET, "HaifuRitsu")

            ''データをセット
            DataGridView1.DataSource = DSET.Tables("HaifuRitsu")

    0は消えてしまいましたが、誤差分が無くなりました。

    色々とありがとうございました。

    どれも、いいアドバイスだったので、失礼とは思いながら全て回答とさせていただきました。



    • 編集済み qoo_man 2012年8月29日 9:43
    2012年8月29日 9:39
  • qoo_man さま 問題解決おめでとうございます。

    あまり、お役に立てなかったようで、恐縮しております。

    後は、 DataGridView の CellStyle で Format の整形をすれば終わりですね。

    2012年8月29日 9:56
  • > データの中は小数点以下4桁までしか入力されておらず、
    > 計算結果を入れているわけではない為、誤差は考えにくいと
    > 思っています。

    コンピュータ内では 2 進数を使うため、ほとんどの 10
    進少数
    は正確に表現できないということは理解されているでしょうか?

    「丸めの誤差」「浮動小数点」などをキーワードにググると参考
    になるページが見つかると思います。例えば下記。

    第4回 演算誤差の正体
    http://pc.nikkeibp.co.jp/pc21/special/gosa/eg4.shtml

    そんなことは言われなくても承知しているということでしたら失
    礼しました。
    • 編集済み SurferOnWww 2012年8月29日 13:16 誤字訂正
    2012年8月29日 13:15
  • ShiroYuki_Mot様
    色々とありがとうございました。

    今後も、不明な点があったら質問すると思いますので、よろしくお願いいたします。

    2012年8月30日 0:08