dポイントプレゼントキャンペーン実施中!

お世話になります。

C#にて、文字列からなる計算式
string s = "(3270+(5*4))/7";

のようなものを計算して
値を返す処理を作成したいと思っています。

http://dobon.net/vb/dotnet/programing/eval.html
↑上記のサイト様から、DataTable.Computeで
求める方法を参考にしたのですが、戻り値を
decimalで受けたいのですが、decimalで取得することができません。
doubleでは取得できますが、decimalでcastしようと
すると、InvalidCastExceptionが発生します。

string exp = "(1+6)*5/(7-4)";

//式を計算する
System.Data.DataTable dt = new System.Data.DataTable();
decimal result = (decimal)dt.Compute(exp, "");
       ↑この行で発生

要は、intやdoubleでは収まらない巨大な桁を持つ文字列の
計算をさせたいのですが、どうすればよいでしょうか。

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

A 回答 (5件)

decimal result = decimal.Parse (dt.Compute(exp, "").ToString());

この回答への補足

回答ありがとうございます。

惜しいところまでは行くのですが、計算結果が
指数("1.234560E+002")等の答えになった時に、変換できないと
例外が出てしまいます。

補足日時:2014/06/23 16:34
    • good
    • 1
この回答へのお礼

よくよく考えてみべましたら、桁があふれるほどのものは
要求する必要がなく、事前に文字列の長さを測って、
長ければ受け付けないようにすればいいのだと気がつきました。

いろいろな方から回答をいただきましたが、本件に一番近い
回答でしたので、ベストアンサーにさせていただきます。

お騒がせいたしました。

お礼日時:2014/06/24 15:07

この回答はまともな解答になっているかどうか。

。。

まず、Computeが何のオブジェクトを返しているか、デバッグして確かめて見る必要があるかと思います。もし、Int32だったら、もうそれまでですし。
あるいは、sの計算式にSQLのdecimalへのキャスト関数のようなものをつけるという方法もあるでしょう。そうすれば、decimalで返ると思います。
最悪、多倍長精度計算のライブラリをどこかから探してくるという方法も考えられます。その場合、自分で式を計算するプログラムを組む必要があるでしょう。でも、decimalでいいんですよね?

この回答への補足

回答、ありがとうございます。

計算結果は、decimalであれば十分だと思っています。

普通にdoubleで拾えるのだから、decimalででも拾えるかと思っていたのですが、
なかなかうまくいかないみたいです。

数値が指数にさえならなければ、No.1さんの答えでも
上手くいったのですが、そこがクリアできればと
思うのですが…。

補足日時:2014/06/23 17:42
    • good
    • 0

たとえば 1 / 3 の結果は"無限の桁"となりますが、いかがいたします?

この回答への補足

そうですよね。

電卓に似せようと思っているので、結果的には
『0.33333333333333333334』などのようにしたいと
思います。

こういう場合もあるんですよね。
ご指摘、ありがとうございます。

補足日時:2014/06/23 16:49
    • good
    • 0

64ビットで受けるならば、Int64と言う型があったと思います。



doubleは通常8バイト精度だったはずです。

ですので通常の型変換でも32ビットのInt32のサイズだと、警告がますね。

ライブラリの使い方などは、製作者に依存しますので、
私の方では、お答えできませんが、

仕事でExcelのデータを読み出して、シートの内容を再計算するライブラリを

C#で書き下したことがあります。

意外と簡単で、3時間くらいで組みあがりましたよ。
そこで、今回はこれの作り方など、考え方をご紹介します。



C#はC言語に比べて生産性が高いので、スピードを要求するような処理以外なら、

手製でライブラリを作成しても早いです。


では、

数式を分解して演算するライブラリの自作について考えて見ましょう。


Step1)数値表現をするクラスを作る

通常はデフォルトで用意されている、double、int などの型を使いますよね。

しかし、ご質問のテーマでは膨大な桁をもつ数値を取り扱うわけです。

ですのでこのStep1がどうしても必要になります。

数値型をデフォルトで用意されていると、恐れ多く感じますよね。

自分で作れないと思ってしまいます。

ですが良く考えると、幾つかのメソッドがあれば事足ります。

ここでは、

"1234567891234567891234567891234"のような数値を取り扱えるクラスを考えます。

プログラム表現は長くなるので割愛します。

一つ一つは簡単なループ文で作成できると思いますので、デバッグをしっかりやれば、

ちゃんと動くと思います。

では、メンバとメソッドを定義します。

以下がクラスの概要です。

class OriginalValue

○メンバ

・string Value; 文字列表現として保存しておくための string 領域
・double[] val; 演算をするための doulbe[] 配列領域 簡単のために10進で桁単位に格納します。
・int floatIndex; 小数点以上、と小数点以下のが何桁目であるか、valのインデックスで記録します。
・string Error; 0での除算等を発見した場合、ここにエラーを書いておきます。

 exp) "1234.56" ⇒ [6.0][5.0][4.0][3.0][2.0][1.0] , floatIndex = 2;

 格納順は自分で決めておきましょう。
 このあとで、
 小学校の算数と同じように、四則演算での桁上がりを行って、四則演算行うメソッド
 を実装します。簡単なループ文でのメソッドを4つ作るだけです。
ここで二つのOriginalValueを使って演算するとき、小数点の位置を先に意識します。
 対応する桁同士を演算します。これにより、小数点も扱うことが出来ます。

○メソッド
・OriginalValue( string val )
コンストラクタ1、
 string で初期値を指定して、double[]演算領域,floatIndexに格納する
・OriginalValue( double val )
コンストラクタ2、
 val を10で割りながら、double[]演算領域,floatIndexに結果を格納する 
・Set( string val )
 コンストラクタ1と同様処理
・Set( double val )
 コンストラクタ2と同様処理
・ToString()
 演算領域を文字列(小数点も意識)になおして、string領域に格納し、それを返却する
・Add( OriginalValue val ) 桁上がりの足し算演算をfor文などを使ってプログラムする
・Sub( OriginalValue val ) 桁上がりの引き算
・Mul( OriginalValue val ) 桁上がりの掛け算
・Div( OriginalValue val ) 桁を考慮した掛け算

Step2) 書式解析を行うクラスを作る

"(34578881003455 + ( 56633566565.0 ) / (565655655) )"
の様な演算式を解析し、結果をOriginalValueで返却します。
(もちろんこれを後でOriginalValueのメソッドに加える)

書式の解析は段階をおいておこないます。
括弧があると、その中の演算を先にしないといけません。
そのため再帰処理(自分自身をコールする)を使用します。
また、括弧が無い場合でも、乗除算は先にしないといけません。
そのため、最初に'+'や'-'記号で文字列をスプリットし、
その中身を更に乗除算で先に処理します。
戻り値が数値の文字列であれば、コレを使って置換してしまうことで、
数式を表す文字列が徐々に数値を表す文字列に変換されていきます。

以下のアルゴリズムになります。

(1)書式の頭から'('を探し、見つけたら、')'までを抜き出し、自分自身を再帰コールする
 ただし、ネストがありうるので、'('の数を数える等して、対応する')'を見つけ、
 '('でネストしている文字列を正確に抜き出します。
 戻ってきた値(数値を表す文字列:OriginalValueのToStiring()で返却)
 で、'('~')'までを文字列置換し、この書式自体を直してしまいます。
 '(’がネストされていても再帰中の(1)で処理され、最後は数値になっているはずです。
 最後まで到達したら、変換された書式結果を使って(2)へ進みます。

(2)(1)の処理を通過(つまり'('がない書式)した次は、以下の四則演算ルーチンをコールする

 四則演算ルーチン概要:

 A.加減算
 ・ 四則演算ルーチンのコール先です。最初に加減算で分解します。  
 ・ '+' , '-' で文字列をスプリットします。この演算記号も取っておきます(※1)
 ・ スプリットしても文字列配列要素が一つである場合は、
   処理せずにそのまま入力を返します。
   このとき数値ではない文字列、””や"-",","-"などの記号である場合は、
   ”0”を返却します。
 ・ スプリットされた文字列を B.乗除算ルーチンに渡し、数値文字列に変換してもら
   います。B.乗除算ルーチンの戻り値で、分離した文字列配列の要素を置換します。
 ・ スプリットされた文字列全てが数値文字列に変換された場合、
 ・ ここでは※1でとって置いた通りの加算、減算を順番に行います。
   このときOriginalValue のAdd,Subメソッドを使用します。
   最終値を数値文字列として返却します。

 B.乗除算 
 ・ '*' , '/' で文字列をスプリットします。この演算記号も取っておきます(※2)
 ・ スプリットしても文字列が一つである場合は、処理せずにそのまま入力を返します。
   このとき数値ではない文字列、””や"-",","-"などの記号である場合は”0”を返却します。
 ・ スプリットされた文字列に対して、OriginalValue のMul,Div
   メソッドを使って演算をし、文字列を返却します。
   0で割らないように気をつけます。0での除算を見つけた場合は、前述Errorエリアに
   エラーメッセージ等を書き、処理を中断。戻り値は”0”で脱出します。
   既に別の場所でエラーを検出している場合もあるので、上書きをしないようにします。
 ・ エラーが無い場合は、※2でとって置いた通りの乗算、加算を順番に行っていきます。
   最終値を数値文字列として返却します。

(3)四則演算ルーチンからの数値文字列を返却します。

Excelなどでは、ROUND(xx)などの様なマクロが発見されますが、
これに対応するためには、先に上記の書式解析演算プログラムを作り、
どこでマクロ文字列を抜き出せるか、ステップトレースすれば分かります。
ここで、マクロ判定を行い、自分で計算して値を返却すればよいのです。

こうすれば、どんなにマクロが増えてもその度に、そこだけ追加すれば対応できます。

便利なライブラリを探して使い方が分かるまで、結構な時間が掛かります。

DataGridなども、自作すると2日も掛かりません。

しかし、こういった高機能ライブラリは、自作で対応するよりも、
メソッドのオーバーライドなどを覚えておくと良いと思います。

C#は、ライブラリを自作すると言う意味で非常に優れています。
他のライトウェイト言語に進むと、表現力が乏しくなったり、出来ることが少なくなる
場合もあります。

また、C++から如何にして生産性を上げるかと言う点で、練りこまれています。

この思想は示唆に富んでおり、C++ベースで開発を行う場合も、
C#で用意されている同等のメソッドや仕組みを導入する事が出来ます。

相乗して効果があるので大変勉強になりますよ。

使い方を調べて、質問をしていると、ここで差が付いてしまいます。

「自分で作ったら意外と簡単だった」

この喜びが、ソフトウェア作りの原動力です。

楽しんで学んで、楽しんで糧を得ましょう。

以上、ご参考になれば
    • good
    • 0
この回答へのお礼

丁寧なご回答、ありがとうございます。

既存の方法でできるのであればと思っていたのですが、
やはりそこまではうまくいかないみたいです。

でも、教えていただいた方法も試してみたいと思います。

参考にある方法、ありがとうございました。

お礼日時:2014/06/23 17:47

>doubleでは取得できますが、decimalでcastしようとすると、InvalidCastExceptionが発生します。



DataTable.Computeの戻り値のインスタンスがdecimalにキャストできるようなクラスでないのでしたらInvalidCastExceptionは当然発生します。

>要は、intやdoubleでは収まらない巨大な桁を持つ文字列の計算をさせたいのですが、どうすればよいでしょうか。

そのような式を表す文字列の評価を行ってくれるメソッドを自分でつくる。

>http://dobon.net/vb/dotnet/programing/eval.html

で参考にされたのは、その自分で作るというのをしないで楽するための方法です。
最初の方には「自分で解析する方法」というのも書かれているでしょ?
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!