トップ回答者
C# 数値計算の誤差 inline関数と型について

質問
-
こんにちは。
C#で、理化学的な計算をできないかテストをしています。物理計算ではないので、あまり情報落ちが発生するのは好ましくないと考えています。
そこで気になっていることについて教えて頂きたいです。
1.Inline関数について
C++は、Inline関数がありました。
C#は、defineと同じような理由で、できないのでしょうか?
Inlineで式を見やすくしたかったのですが、関数やゲッターにすると、=1/3のような時などに情報落ちが都度都度、起きないのでしょうか?
2.型と式について
計算途中は、割り切れない数や小さな値や、大きな値になると思います。
そのため、なるべく情報落ちなどの誤差の発生しないように、まとめるべきなのではないか、と考えています。
また、Excelの計算結果となるべく合うようにしたいと思い、変数型にはdecimalがよいのではないかと考えています。
C#はコンパイル後には、適時インライン化されているということも聞いたことがありますが、どこまでしてくれているのか分からないため、できるなら明示的にしていたいです。処理自体は、ゲームのように繰り返し再計算するものではありません。求める答えのいくつかを1度計算をすれば、計算自体の処理は達成するものです。
以上の2点について、考え方に間違いがあるなど、分かる方いましたら教えて下さい。
お手数おかけしますが、よろしくお願いします。
- 編集済み ichiethel 2014年2月4日 9:41
回答
-
ans = 1013.25M * now / 1013.25M; // このように置き換えられる?(特にinlineを指定すればdefineの置換のように必ずこうなると考えていた)
C言語及びC++言語のプリプロセッサ(#define)は計算前に展開されるため挙げられているように展開することは可能です。ただし条件があり、1ヶ所も丸かっこ()を含まないことであり、先に挙げられた式にも (now / 1013.25M) と書かれているように
ans = 1013.25M * (now / 1013.25M);
に展開されます。C++言語のinlineも同じく先に関数の値を出すため、演算順序を入れ替えることはできません。このことは誤差に影響します。プログラミングに関係なく、学校で習う数学の「誤差」については理解されているでしょうか? 誤差が積算される演算を行う限りはdoubleだろうとdecimalだろうと精度が得られないのは同じです。
また比較対象としてExcelを挙げられていますが、ではExcelの精度は確認されていますでしょうか?
# 手元のOffice Excel 2013で =1/3 を実行しましたが、 0.333333333333333000000 となり15桁しか表示されませんでした。decimalは有効精度は28桁ありますが最大値は7.9×10<sup>28</sup>までしかありません。doubleは有効精度は15桁しかありませんが最大値は1.7×10<sup>308</sup>まで扱えます。適材適所で選んでください。
- 回答としてマーク ichiethel 2014年2月4日 3:45
すべての返信
-
また、Excelの計算結果となるべく合うようにしたいと思い、変数型にはdecimalがよいのではないかと考えています。
Excelとは関係なく、decimalを使うべきです。以下を参考にして下さい。
小数(浮動小数点数型)の計算が思った結果にならない理由と解決法
http://dobon.net/vb/dotnet/beginner/floatingpointerror.html★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
-
trapemiyaさん 返信ありがとうございます。
Excelとなるべく同じにしたいと考えたのは、都合的な部分でExcelで手計算をしたけど答えが合わない、といった部分です。Excelの計算精度はよく分かりませんが、それを正とする人が不特定多数いるため、Excelに合わせたいな、という意思もありました。
とはいえ、それだとなるべく精度を上げたいという意思と、Excelに合わせたい、という意思では微妙にズレがありそうです。やはり本来の誤差はなるべくないようにするというのが正しいと考えています。
なので、trapemiyaさんの指摘頂いている通り、型については、decimalにしたいと思います。
-
仲澤さん 返信ありがとうございます。
無関係という点については、すいません。まだ、このコメントによってそういう意見を知った所までで、仕様までは確認できていません。そのため、曖昧な表現にしてしまいました。気を悪くされたなら申し訳ないです。
指摘頂いている1,2についてはその通りだと考えます。ただ、知識が足りず曖昧な理解で困っています。
1については、メソッドなどを一度も通さず、中間出力結果なしにやるほうが精度はよいのでしょうか?
単純な計算、例えば ATM'=現在の気圧/標準気圧のような割り切れないものを用意すれば、やはり0.33333.....のような割り切れない計算値は非常に小さなものですが有効桁までで落ちてしまうので、最終式にまとめた方がよいとなるのでしょうか。
掛け算だけの途中式や X含有量=質量*理論含有率/100くらいは、情報落ちしないだろうということで、途中式にして良いと考えています。
- 編集済み ichiethel 2014年2月3日 10:05
-
インラインを考えた理由としては、後からソースが若干見やすくなるか、と考えました。単に、関数化するくらいならinlineのほうが式を展開する分適切なのではないか、というような理由でもありました。
まず質問者さんの言われる「インライン」とは何でしょうか? 一般に言われるインラインとは別のものを指しているようでこれでは他者と意思疎通が不可能です。
ところで実行したい計算は四則演算だけでしょうか? 三角関数や指数関数などは使わないものなのでしょうか? これらはdouble精度のメンバーしかありませんし、さらにCPUの精度に依存します。ですのでこれらの値を含む場合はdecimalで計算しても精度が出ることはありません。
-
外池と申します。「理化学」の意味がわかりかねますが・・・、物理や化学のための数値解析は、概ね、微分、積分などの計算、行列計算を応用した多元連立方程式を解く計算などに帰着されると思います。このような数値解析を精度も含めて解説した書物が多く出版されていますので、まず、そちらを勉強されることをお勧めします。
さらに、典型的な計算手法の具体的なコーディングを示した解説書も出ていますので、そういうものを参考にしてプログラムして、さらに、ご自身でベンチマーク計算をやって精度を確認されるのがよろしいかと。
小生のお勧めはNumerical Recipesです。
http://www.nr.com/佐祐理さんがご指摘になっているとおり、FPUに搭載されている三角関数、指数関数、対数関数等を使うのであれば、結局のところ、double型で計算を組み立てるしかありません。で、小生が知る範囲では、大抵の場合、十分な精度が得られます。
Excelと全ての桁を一致させよう、という目標設定は、本質的とは思えません。
- 編集済み 外池 2014年2月3日 13:05
-
佐祐理さんがご指摘になっているとおり、FPUに搭載されている三角関数、指数関数、対数関数等を使うのであれば、結局のところ、double型で計算を組み立てるしかありません。
ちょっと説明が言葉足らずだったかもしれませんが、C#で計算する以上、Math.Sin()などのメソッドを使用するしかありませんが、そこで用意されているオーバーロードがdoubleのみということを伝えたかったのです。
# まさか多項式展開とかして目標精度まで計算するというわけでもないでしょうし。 -
佐祐理さん 返信ありがとうございます。
インライン関数について、仰られているとおり自分に間違いがあったようです。
仲澤さんのコメントより
>インライン関数等の「インライン」の指定、
>すなわち inline、__forceinline の有無は、
>それを指定された関数内の処理、ないし計算の精度とは無関係です。
>また、当たり前ですが、インライン指定したからといって、
>必ずインライン展開されるわけではありません。
>実際に展開するかどうかはコンパイラが勝手に決めます。
とありますので、この部分は佐祐理さんの言われるように、自分の考え方に間違いがあるようです。
特に指定された関数内の処理、ないし計算の精度とは無関係というところが、自分の考えと違いがあるため間違いがあるようです。
インライン展開するかどうかをコンパイラが勝手に決める、ということなので、その部分が原因だと考えています。
例:単純な式
decimal atm_correction(decimal now){ return now / 1013.25M; }
の部分を、例えば下にします
decimal atm_correction(decimal now, Standard std)
{
if (std == STP_before) return (now / 1013.25M);
else if (std == STP_after) return (now / 1000M);
else if (std == SATP) return (now / 1000M);
else return (now);
}
上の場合に、元のプログラミングで書いた式が次とすると
ans = 1013.25M * now / atm_correction(now, std);
(仮の式なので意味の無いものですが)
上は
ans = 1013.25M * now / 1013.25M; // このように置き換えられる?(特にinlineを指定すればdefineの置換のように必ずこうなると考えていた)下は動きとしては、このようになってしまうのではないか、という決め付けをしていました。
var correction = ATM(now); // ここで割り切れない答えになってる?
ans = std.atm * correction;
特にこれに対して理解がなかったようです。
もう一点指摘頂いた、三角関数や指数関数、Pow()やSin()は使用しないです。
演算結果がCPUの精度に依存するというのも知らなかったです。午後にもう少し時間が持てそうなので、もうちょっと調べてみたいと思います。
-
ans = 1013.25M * now / 1013.25M; // このように置き換えられる?(特にinlineを指定すればdefineの置換のように必ずこうなると考えていた)
C言語及びC++言語のプリプロセッサ(#define)は計算前に展開されるため挙げられているように展開することは可能です。ただし条件があり、1ヶ所も丸かっこ()を含まないことであり、先に挙げられた式にも (now / 1013.25M) と書かれているように
ans = 1013.25M * (now / 1013.25M);
に展開されます。C++言語のinlineも同じく先に関数の値を出すため、演算順序を入れ替えることはできません。このことは誤差に影響します。プログラミングに関係なく、学校で習う数学の「誤差」については理解されているでしょうか? 誤差が積算される演算を行う限りはdoubleだろうとdecimalだろうと精度が得られないのは同じです。
また比較対象としてExcelを挙げられていますが、ではExcelの精度は確認されていますでしょうか?
# 手元のOffice Excel 2013で =1/3 を実行しましたが、 0.333333333333333000000 となり15桁しか表示されませんでした。decimalは有効精度は28桁ありますが最大値は7.9×10<sup>28</sup>までしかありません。doubleは有効精度は15桁しかありませんが最大値は1.7×10<sup>308</sup>まで扱えます。適材適所で選んでください。
- 回答としてマーク ichiethel 2014年2月4日 3:45
-
今さらながらの蛇足レスですが、inline 関数について The C++ Programing Language 2nd Edition 4.6.2 Function Definition に書いてあった事が参考になると思ったので、一部抜粋しておきます。
*** Quote ***
As mentioned, a function can be defined to be inline. For example:inline fac(int n) { return i<2? 1 : n*fac(n-1); }
The inline specifier is a hint to the complier that it should attempt to generate code for a call of fac() inline rather that laying down the code for the function once and then calling through the usual function call mechanism. A clever complier could generate the constant 720 for a call fac(6). The possibility of mutually recursive inline functions, inline function that recurse or not dependent on input, etc., makes it impossible to guarantee that every call of an inline function is actually inlined. The degree of cleverness of complier cannot be legislated, so another complier might generate 6*5*4*3*2*1, another 6*fac(5) and yet another an unoptimized call fac(6).
*** Unquote ***