トップ回答者
【C#】Access上の数値型(倍精度浮動小数点型)、通貨型(小数点2位まで表示)の型変換について

質問
-
お世話になります。
C#のWindowsフォームでの操作でご質問があります。DataGridView上のバインドさせたフィールドを使って計算させた
値(「数量」×「単価」)を、非バインドで追加した列(「金額」)に表示させたい
と思っておりますが、型変換が上手くいかずエラーとなってしまいます。エラーの回避方法をお教えいただきたいと思っております。
恐れ入りますがよろしくお願い致します。【環境】
・作成しているもの:VisualC#のWindows Forms アプリ
・OS:Windows7
・.NET のバージョン: 4.7.02053
・C#のバージョン:visual studio 2017
・接続データベース:Access2003 SP3<Access側の型>
数量:数値型(倍精度浮動小数点型)DataGridViewの4列目に表示
単価:通貨型(小数点2位まで表示)DataGridViewの5列目に表示
金額:Access側テーブルにフィールド無し。DataGridViewの6列目に非バインドで追加し表示のみ<数量でのエラー内容>
System.InvalidCastException: '型 'System.Double' のオブジェクトを型 'System.String' にキャストできません。'<単価でのエラー内容>
System.InvalidCastException: '型 'System.Int32' のオブジェクトを型 'System.String' にキャストできません。'<記載したコーディング>
private void A_DataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if ((A_DataGridView[4, e.RowIndex].Value != null) && (A_DataGridView[5, e.RowIndex].Value != null))
{
double v1 = double.Parse((string) A_DataGridView[4, e.RowIndex].Value);
double v2 = double.Parse((string) A_DataGridView[5, e.RowIndex].Value);
A_DataGridView[6, e.RowIndex].Value = (v1 * v2).ToString();
}
}
回答
-
エラーメッセージから推測するに、A_DataGridView[4, e.RowIndex].Value が double 型、A_DataGridView[5, e.RowIndex].Value が int 型にキャストできると思いますがそうであれば、
double v1 = double.Parse((string) A_DataGridView[4, e.RowIndex].Value); double v2 = double.Parse((string) A_DataGridView[5, e.RowIndex].Value);
を以下のようにすればよいのでは?
double v1 = (double) A_DataGridView[4, e.RowIndex].Value; int v2 = (int) A_DataGridView[5, e.RowIndex].Value;
そうすれば、少なくとも今出ている例外は出なくなり、(v1 * v2).ToString(); も問題ないはずです。
- 編集済み SurferOnWww 2018年6月13日 3:10 int ⇔ double 訂正
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
【追伸】
以下のところ、間違いないですか? よくチェックしてみてください。
> <Access側の型>
> 数量:数値型(倍精度浮動小数点型)DataGridViewの4列目に表示
> 単価:通貨型(小数点2位まで表示)DataGridViewの5列目に表示自分の環境にある Northwind 2003.accdb の Products 2003 テーブルの数値型、通貨型は(以下の画像参照)、それから Visual Studio のウィザードで型付 DataSet を作ると、当該列の型はそれぞれ short, decimal となります。
その型付 DataSet をデータバインドして作られる DrarGridView の当該列の型は同じく short, decimal となります。以下の画像を見てください。
そこが間違っていると、上記の質問は意味がないです。
- 編集済み SurferOnWww 2018年6月13日 4:43 訂正
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
> .mdb ファイルを修正後、アプリケーションを作り直して検証してみましたが、同じエラーメッセージとなりました。
やり方の問題だと思います。はっきり言うと、質問者さんのやり方がどこか間違っているとしか思えません。
そこまで言って、そうでは無かったりすると何なので、.mdb ファイルのサンプルを作り、JET プロバイダを使って検証してみました。
以下に示すように期待通りの結果になります。
.mdb ファイル
JET プロバイダ使用
問題部分の C# のコード
結果
今のままだと丸めの誤差で計算結果が 1 銭単位で違ってくる可能性が大です。なので、このまま対症療法を続けるのではなく問題の原因を調べて、あるべき姿に近づけてはいかがですか。
質問者さんはそこまでやる気はなさそうな気もしますけど、もしそうであればそう言ってください。これで終わりにしますので。
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
v1およびv2が0なのは、初期値の0であって、まだ何も値がセットされていないからだと思います。
ブレークポイントはv1の行に設定しませんでしたか? ブレークポイントで止まった行はまだ実行されていません。つまり、ブレークポイントはその止まった行が実行される直前で停止している状態です。知りたいのは、上でも書きましたが、A_DataGridView[4, e.RowIndex].Value と A_DataGridView[5, e.RowIndex].Value の値です。ブレークポイントで止まった時、マウスポインタはそれぞれのValueの上に持って行って下さい。それぞれのValueが表示されますが、それがsasatomoさんが思っている値でしょうか?
イミディエイトウィンドウに、
A_DataGridView[1, 3].Value
のように添え字を適当に変えて値を確かめてみて下さい。自分の思った通りの値が表示されますでしょうか?★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
Access 2003ということはMicrosoft Jet Database Engine 4.0が使われているはずです。Data Type Support (OLE DB)によると通貨型はDBTYPE_CYであり、OLE DB データ型のマッピングによるとDBTYPE_CYはDecimal型として扱われることになっています。
にもかかわらず、投稿されたようなエラーが発生するということは、質問者さんが説明されている内容と実際の設定内容が異なっているものと思われます。
ともあれ、接続方法はこれ以外にもあり、ODBCを経由すると通貨型が存在しないため、SQL_DOUBLEからのDouble型と扱われることもあるでしょうし、Access 2007からはACEと呼ばれる新しいプロバイダも導入されていて、どの方式で接続しているのかはわかりづらくもあります。そこで、最初に説明したように、Convert.ToDouble(Object)メソッドを使用すれば、Int32であれDoubleであれ数値っぽい型や数値を含むString型などどのような型であっても.NETラインタイム側が適切に処理してDouble型を返してくれます。プログラムの構成上、数値っぽいものが得られることが分かっているのであれば、あまり悩まずConvert.ToDoubleされることをお勧めします。
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
データベースへの接続方法次第で.NET型は変わり得ると思います。その上で、質問文には例外メッセージが提示されているので、それが真として回答すれば十分に思います。
質問文を信じて回答したら大ハズレということが過去何回かあったような気がしますが、とりあえず信じて最初の回答をしています。でも、確認は必要。また自分の回答が大ハズレになるかもしれませんので。
単価 x 数量の数量を「倍精度浮動小数点型」にするのは少なくとも自分的にはあり得ないし、単価を「通貨型」にしたのに型付 DataSet の当該列の型が Int32 になるのは解せないので。
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
> 始めに教えていただいた方法でエラーの回避が出来ました。
「初めに」とはどれですか? 最適解と判断されたのでしょうか? 「初めに」案を採用した理由を教えてください。
> こちらでも調査し検証致しました。
調査結果を書いていただけませんか。
最初の質問に書いてあった「数値型(倍精度浮動小数点型)」が正しければそれが double になるのは納得ですが、「通貨型」が decimal ではなく Int32 になるのが解せません。
自分の環境と違いがあるとすると、多分質問者さんの方は Access ファイルは .mdb(自分は .accdb)、データベースエンジンは JET(自分は ACE)ぐらいしか思い当たりませんが、そのあたりはいかがですか?
このフォーラムは技術者同士の情報交換の場として提供されているそうですので、質問者さんにも情報提供に協力していただけると幸いです。
- 回答としてマーク sasatomo 2018年6月14日 6:33
-
対症療法で済ませたという感じがしますが、対症療法ばかりではそのうち破綻するような気がします。
何でもいからできれば OK という訳でなければ、あるべき姿を考えて設定・コーディングしませんか?
基本的に、「単価」x「数量」を扱う場合、丸め誤差を最小限に抑えるために「単価」には decimal 型を、「数量」には整数型(short, int, long など)を使うことを考えてください。
($ と違って、小数点以下は扱わないということであれば「単価」も整数型でもいいかもしれませんが、それはちょっと置いといて)
元となる Access で、「単価」は実は「通貨型」になっているとのことなのでそれはそのままとし、「数量」は「数値型(倍精度浮動小数点型)」ではなくてデフォルトの「数値型(長整数型)」に変更しましょう。
そうすれば、型付 DataSet で「単価」は decimal、「数量」は int になるはずです。それがあるべき姿だと思います。
そうすれば、DataGridView の 4 列が「数量」、5 列が「単価」であれば、以下のようにできるはずです。
int v1 = (int) A_DataGridView[4, e.RowIndex].Value; // 数量 decimal v2 = (decimal) A_DataGridView[5, e.RowIndex].Value; // 単価
また、v1 * v2 の結果は decimal 型になります。丸め誤差は最小限に抑えられるはずです。
以上のようにすれば、A_DataGridView[4, e.RowIndex].Value や A_DataGridView[4, e.RowIndex].Value を double 型に変換する必要はありません。というより、丸めの誤差が出るという好ましからざる結果を生むかもしれません。
- 編集済み SurferOnWww 2018年6月14日 7:46 訂正
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
int v1 = (int) A_DataGridView[4, e.RowIndex].Value; // 数量
decimal v2 = (decimal) A_DataGridView[5, e.RowIndex].Value; // 単価
とコーディング致しましたが、
System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'
System.Windows.Forms.DataGridViewCell.Value.get が null を返しました。
ちなみに ブレークポイントで止めて、A_DataGridView[4, e.RowIndex].Value と A_DataGridView[5, e.RowIndex].Value の値はどうなっていますか?
これがnullであれば、DataGridViewにうまく値が渡っていないか、4や5などのインデックスが違っている可能性があります。
いずれにしてもデバッグの問題ですので、適当なところでブレークポイントで止め、自分の思った通りの値が来ているかチェックしてみて下さい。ブレークポイントで止まった時に、イミディエイトウィンドウにA_DataGridView[4, e.RowIndex].Valueなどを打ち込んで確認することもできます。★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 回答としてマーク sasatomo 2018年6月19日 7:23
すべての返信
-
エラーメッセージから推測するに、A_DataGridView[4, e.RowIndex].Value が double 型、A_DataGridView[5, e.RowIndex].Value が int 型にキャストできると思いますがそうであれば、
double v1 = double.Parse((string) A_DataGridView[4, e.RowIndex].Value); double v2 = double.Parse((string) A_DataGridView[5, e.RowIndex].Value);
を以下のようにすればよいのでは?
double v1 = (double) A_DataGridView[4, e.RowIndex].Value; int v2 = (int) A_DataGridView[5, e.RowIndex].Value;
そうすれば、少なくとも今出ている例外は出なくなり、(v1 * v2).ToString(); も問題ないはずです。
- 編集済み SurferOnWww 2018年6月13日 3:10 int ⇔ double 訂正
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
【追伸】
以下のところ、間違いないですか? よくチェックしてみてください。
> <Access側の型>
> 数量:数値型(倍精度浮動小数点型)DataGridViewの4列目に表示
> 単価:通貨型(小数点2位まで表示)DataGridViewの5列目に表示自分の環境にある Northwind 2003.accdb の Products 2003 テーブルの数値型、通貨型は(以下の画像参照)、それから Visual Studio のウィザードで型付 DataSet を作ると、当該列の型はそれぞれ short, decimal となります。
その型付 DataSet をデータバインドして作られる DrarGridView の当該列の型は同じく short, decimal となります。以下の画像を見てください。
そこが間違っていると、上記の質問は意味がないです。
- 編集済み SurferOnWww 2018年6月13日 4:43 訂正
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
データベースへの接続方法次第で.NET型は変わり得ると思います。その上で、質問文には例外メッセージが提示されているので、それが真として回答すれば十分に思います。
質問文を信じて回答したら大ハズレということが過去何回かあったような気がしますが、とりあえず信じて最初の回答をしています。でも、確認は必要。また自分の回答が大ハズレになるかもしれませんので。
単価 x 数量の数量を「倍精度浮動小数点型」にするのは少なくとも自分的にはあり得ないし、単価を「通貨型」にしたのに型付 DataSet の当該列の型が Int32 になるのは解せないので。
- 回答としてマーク sasatomo 2018年6月14日 1:35
-
SurferOnWww様
いつもありがとうございます。
始めに教えていただいた方法でエラーの回避が出来ました。
ありがとうございました。また、Access側の詳細まで詳しく調査して下さり、
お手数をおかけしました。こちらでも調査し検証致しました。
SurferOnWww様のおっしゃる通り数量を「倍精度浮動小数点型」にする
必要は無いように思いましたので、Accessのmdb作成者にその意図を確認し、
整備していきたいと感じました。本当にありがとうございました、 感謝申し上げます。
- 回答としてマーク sasatomo 2018年6月14日 1:35
- 回答としてマークされていない 立花楓Microsoft employee, Moderator 2018年6月14日 1:54
-
> 始めに教えていただいた方法でエラーの回避が出来ました。
「初めに」とはどれですか? 最適解と判断されたのでしょうか? 「初めに」案を採用した理由を教えてください。
> こちらでも調査し検証致しました。
調査結果を書いていただけませんか。
最初の質問に書いてあった「数値型(倍精度浮動小数点型)」が正しければそれが double になるのは納得ですが、「通貨型」が decimal ではなく Int32 になるのが解せません。
自分の環境と違いがあるとすると、多分質問者さんの方は Access ファイルは .mdb(自分は .accdb)、データベースエンジンは JET(自分は ACE)ぐらいしか思い当たりませんが、そのあたりはいかがですか?
このフォーラムは技術者同士の情報交換の場として提供されているそうですので、質問者さんにも情報提供に協力していただけると幸いです。
- 回答としてマーク sasatomo 2018年6月14日 6:33
-
SurferOnWww様
結果報告が足りず申し訳ありませんでした。
「数値型(倍精度浮動小数点型)」がdouble、「通貨型」が decimalとなりました。
単価が2種類あり、最初検証していた単価は良く見ると数値型となっていたため、
Int32のエラーとなっていたようでした。
調査ミス、また、言葉足らずで申し訳ありません。SurferOnWww様の初めの回答、
double v1 = (double) A_DataGridView[4, e.RowIndex].Value;
int v2 = (int) A_DataGridView[5, e.RowIndex].Value;
は、数値型の単価ではエラー回避されましたが、
通貨型の単価ではエラーとなりました。(ご連絡せず申し訳ありません)
佐祐理様にお教えいただいた回答で
運用してみようと思っております。
ご報告が足りず申し訳ありませんでした。
慣れないことで、ご迷惑をおかけし申し訳ありませんでした。
ありがとうございます。 -
対症療法で済ませたという感じがしますが、対症療法ばかりではそのうち破綻するような気がします。
何でもいからできれば OK という訳でなければ、あるべき姿を考えて設定・コーディングしませんか?
基本的に、「単価」x「数量」を扱う場合、丸め誤差を最小限に抑えるために「単価」には decimal 型を、「数量」には整数型(short, int, long など)を使うことを考えてください。
($ と違って、小数点以下は扱わないということであれば「単価」も整数型でもいいかもしれませんが、それはちょっと置いといて)
元となる Access で、「単価」は実は「通貨型」になっているとのことなのでそれはそのままとし、「数量」は「数値型(倍精度浮動小数点型)」ではなくてデフォルトの「数値型(長整数型)」に変更しましょう。
そうすれば、型付 DataSet で「単価」は decimal、「数量」は int になるはずです。それがあるべき姿だと思います。
そうすれば、DataGridView の 4 列が「数量」、5 列が「単価」であれば、以下のようにできるはずです。
int v1 = (int) A_DataGridView[4, e.RowIndex].Value; // 数量 decimal v2 = (decimal) A_DataGridView[5, e.RowIndex].Value; // 単価
また、v1 * v2 の結果は decimal 型になります。丸め誤差は最小限に抑えられるはずです。
以上のようにすれば、A_DataGridView[4, e.RowIndex].Value や A_DataGridView[4, e.RowIndex].Value を double 型に変換する必要はありません。というより、丸めの誤差が出るという好ましからざる結果を生むかもしれません。
- 編集済み SurferOnWww 2018年6月14日 7:46 訂正
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
SurferOnWww様
アドバイスをありがとうございます。
「数量」は作成者にも確認した上で「数値型(長整数型)」に変更致しました。
「単価」は、「紙一枚@1.64円」と言うデータがありましたので整数型にはせずに
そのまま通貨型、decimal 型に致しました。その上でint v1 = (int) A_DataGridView[4, e.RowIndex].Value; // 数量
decimal v2 = (decimal) A_DataGridView[5, e.RowIndex].Value; // 単価
とコーディング致しましたが、
System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'
System.Windows.Forms.DataGridViewCell.Value.get が null を返しました。とのエラーになり、Access側の型を整備をしながら修正したからなのか、
それともデータベースエンジン(JETとACE)の違いなのか、
判断がつかなくなってしまい、大変申し訳ありません。お気づきの点がありましたらご教示いただければと思っております。
いつもアドバイスをありがとうございます。 -
> .mdb ファイルを修正後、アプリケーションを作り直して検証してみましたが、同じエラーメッセージとなりました。
やり方の問題だと思います。はっきり言うと、質問者さんのやり方がどこか間違っているとしか思えません。
そこまで言って、そうでは無かったりすると何なので、.mdb ファイルのサンプルを作り、JET プロバイダを使って検証してみました。
以下に示すように期待通りの結果になります。
.mdb ファイル
JET プロバイダ使用
問題部分の C# のコード
結果
今のままだと丸めの誤差で計算結果が 1 銭単位で違ってくる可能性が大です。なので、このまま対症療法を続けるのではなく問題の原因を調べて、あるべき姿に近づけてはいかがですか。
質問者さんはそこまでやる気はなさそうな気もしますけど、もしそうであればそう言ってください。これで終わりにしますので。
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
int v1 = (int) A_DataGridView[4, e.RowIndex].Value; // 数量
decimal v2 = (decimal) A_DataGridView[5, e.RowIndex].Value; // 単価
とコーディング致しましたが、
System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'
System.Windows.Forms.DataGridViewCell.Value.get が null を返しました。
ちなみに ブレークポイントで止めて、A_DataGridView[4, e.RowIndex].Value と A_DataGridView[5, e.RowIndex].Value の値はどうなっていますか?
これがnullであれば、DataGridViewにうまく値が渡っていないか、4や5などのインデックスが違っている可能性があります。
いずれにしてもデバッグの問題ですので、適当なところでブレークポイントで止め、自分の思った通りの値が来ているかチェックしてみて下さい。ブレークポイントで止まった時に、イミディエイトウィンドウにA_DataGridView[4, e.RowIndex].Valueなどを打ち込んで確認することもできます。★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
SurferOnWww様
お忙しいところ、.mdbのサンプルまで作成して
検証して下さりありがとうございます。
お手数をおかけしてしまい大変申し訳ありません。何度かプロジェクトを一から作成してみたのですが、
同じエラーとなりました。
おっしゃる通り私のやり方がどこか間違っているのだと思うのですが、
気づくことが出来ておらず申し訳ありません。Trapemiya様のアドバイスでブレークポイントで止めてみますと、
値がv1,v2共に0となっておりました。
また、イミディエイトウィンドウに値を入れてみますと、
v1は入りましたが、v2は
「error CS0664: 型 double のリテラルを暗黙的に
型 'decimal' に変換することはできません。
'M' サフィックスを使用して、この型のリテラルを
作成してください。」となりました。
大変申し訳ないのですが、もしお気づきの点などがありましたら
ご教示いただければと思っております。
いつもありがとうございます。 -
Trapemiya様
お忙しいところアドバイスをありがとうございます。
ブレークポイントで止めてみますと、
値がv1,v2共に0となっておりました。
インデックスは合っております。
値がうまく渡っていないのでしょうか?
また、イミディエイトウィンドウに値を入れてみますと、
v1は入りましたが、v2は
「error CS0664: 型 double のリテラルを暗黙的に
型 'decimal' に変換することはできません。
'M' サフィックスを使用して、この型のリテラルを
作成してください。」となりました。
もしお気づきの点などがありましたら
ご教示いただければと思っております。
いつもありがとうございます。 -
v1およびv2が0なのは、初期値の0であって、まだ何も値がセットされていないからだと思います。
ブレークポイントはv1の行に設定しませんでしたか? ブレークポイントで止まった行はまだ実行されていません。つまり、ブレークポイントはその止まった行が実行される直前で停止している状態です。知りたいのは、上でも書きましたが、A_DataGridView[4, e.RowIndex].Value と A_DataGridView[5, e.RowIndex].Value の値です。ブレークポイントで止まった時、マウスポインタはそれぞれのValueの上に持って行って下さい。それぞれのValueが表示されますが、それがsasatomoさんが思っている値でしょうか?
イミディエイトウィンドウに、
A_DataGridView[1, 3].Value
のように添え字を適当に変えて値を確かめてみて下さい。自分の思った通りの値が表示されますでしょうか?★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
Access 2003ということはMicrosoft Jet Database Engine 4.0が使われているはずです。Data Type Support (OLE DB)によると通貨型はDBTYPE_CYであり、OLE DB データ型のマッピングによるとDBTYPE_CYはDecimal型として扱われることになっています。
にもかかわらず、投稿されたようなエラーが発生するということは、質問者さんが説明されている内容と実際の設定内容が異なっているものと思われます。
ともあれ、接続方法はこれ以外にもあり、ODBCを経由すると通貨型が存在しないため、SQL_DOUBLEからのDouble型と扱われることもあるでしょうし、Access 2007からはACEと呼ばれる新しいプロバイダも導入されていて、どの方式で接続しているのかはわかりづらくもあります。そこで、最初に説明したように、Convert.ToDouble(Object)メソッドを使用すれば、Int32であれDoubleであれ数値っぽい型や数値を含むString型などどのような型であっても.NETラインタイム側が適切に処理してDouble型を返してくれます。プログラムの構成上、数値っぽいものが得られることが分かっているのであれば、あまり悩まずConvert.ToDoubleされることをお勧めします。
- 回答としてマーク sasatomo 2018年6月19日 7:23
-
-
Trapemiya様
いつもありがとうございます。
申し訳ありません、ブレークポイントをv1に設定しておりましたので、
計算させている位置に設定したところ、
値が入っており、この値は合っておりましたが、
ブレークポイントをどこにも設定せずにデバックを開始すると
int v1 =の位置でSystem.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'
System.Windows.Forms.DataGridViewCell.Value.get が null を返しました。のエラーとなります。
皆様がおっしゃるように、恐らく私のやり方がどこか間違っているのだと思い、
それに気がつくことが出来ずにいるのだと思います。
大変申し訳ありません。
私自身も原因を分かりたいのですが、皆様にもお手数をおかけしてしまいます為、
今回は佐祐理様にお教えいただいた方法で運用してみたいと思います。
いつもありがとうございます。