none
C# 数値計算の誤差 inline関数と型について RRS feed

  • 質問

  • こんにちは。

    C#で、理化学的な計算をできないかテストをしています。物理計算ではないので、あまり情報落ちが発生するのは好ましくないと考えています。
    そこで気になっていることについて教えて頂きたいです。

    1.Inline関数について
    C++は、Inline関数がありました。
    C#は、defineと同じような理由で、できないのでしょうか?
    Inlineで式を見やすくしたかったのですが、関数やゲッターにすると、=1/3のような時などに情報落ちが都度都度、起きないのでしょうか?

    2.型と式について
    計算途中は、割り切れない数や小さな値や、大きな値になると思います。
    そのため、なるべく情報落ちなどの誤差の発生しないように、まとめるべきなのではないか、と考えています。
    また、Excelの計算結果となるべく合うようにしたいと思い、変数型にはdecimalがよいのではないかと考えています。
    C#はコンパイル後には、適時インライン化されているということも聞いたことがありますが、どこまでしてくれているのか分からないため、できるなら明示的にしていたいです。

    処理自体は、ゲームのように繰り返し再計算するものではありません。求める答えのいくつかを1度計算をすれば、計算自体の処理は達成するものです。

    以上の2点について、考え方に間違いがあるなど、分かる方いましたら教えて下さい。
    お手数おかけしますが、よろしくお願いします。




    • 編集済み ichiethel 2014年2月4日 9:41
    2014年2月3日 7:18

回答

  • 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
    2014年2月4日 2:20

すべての返信

  • 処理自体は、ゲームのように繰り返し再計算するものではありません。求める答えのいくつかを1度計算をすれば、計算自体の処理は達成するものです。
    であれば、そもそもinline化の恩恵が得られないように思いますが、なぜinlineにこだわるのでしょうか?
    2014年2月3日 7:52
  • C++言語のインライン関数等の「インライン」の指定、
    すなわち inline、__forceinline の有無は、
    それを指定された関数内の処理、ないし計算の精度とは無関係です。

    また、当たり前ですが、インライン指定したからといって、
    必ずインライン展開されるわけではありません。
    実際に展開するかどうかはコンパイラが勝手に決めます。

    従って、本件の質問内容から察するにインラインのことは忘れてよいでしょう。

    2014年2月3日 8:02
  • 佐祐理さん 返信有難うございます。

    インラインを考えた理由としては、後からソースが若干見やすくなるか、と考えました。単に、関数化するくらいならinlineのほうが式を展開する分適切なのではないか、というような理由でもありました。

    また、式がとても長いので、一先ず関数化、またはインラインにしたらどうか、と単純に思っていました。


    • 編集済み ichiethel 2014年2月3日 8:35
    2014年2月3日 8:30
  • 仲澤さん返信ありがとうございます。

    私はなるべく計算の精度をあげようと思って、インラインと式を1つにすることを思いついたのですが、精度に関わるような部分はどこになるでしょうか?

    ひとまずinlineはC#では使えない?+あまり関係がないという理解をしました。

    (処理速度向上などの部分で活用するのが本筋なのかな?)


    • 編集済み ichiethel 2014年2月3日 8:36
    2014年2月3日 8:34
  • また、Excelの計算結果となるべく合うようにしたいと思い、変数型にはdecimalがよいのではないかと考えています。

    Excelとは関係なく、decimalを使うべきです。以下を参考にして下さい。

    小数(浮動小数点数型)の計算が思った結果にならない理由と解決法
    http://dobon.net/vb/dotnet/beginner/floatingpointerror.html


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年2月3日 8:59
    モデレータ
  • 自分は「無関係」と述べました。説明するまでもありませんが、
    これは一般に、「あまり関係がない」のではなくて、
    「まったく関係がない」ことを意図する場合に使用する用語です。
    また、憶測は役に立たないことが多いので。納得できない場合は、
    言語のマニュアルを読んだ方が良いでしょう。

    一般に「計算精度」を上げるには、どのような言語であっても、

    1.入力、中間演算結果、出力等に用いる、変数の「厳密さ」を上げる。
    2.演算自体の「厳密さ」を上げる。

    に気をつけてコードするだけです。
    それぞれの具体的な手法は、その目的に依存します。

    2014年2月3日 9:11
  • trapemiyaさん 返信ありがとうございます。

    Excelとなるべく同じにしたいと考えたのは、都合的な部分でExcelで手計算をしたけど答えが合わない、といった部分です。Excelの計算精度はよく分かりませんが、それを正とする人が不特定多数いるため、Excelに合わせたいな、という意思もありました。

    とはいえ、それだとなるべく精度を上げたいという意思と、Excelに合わせたい、という意思では微妙にズレがありそうです。やはり本来の誤差はなるべくないようにするというのが正しいと考えています。

    なので、trapemiyaさんの指摘頂いている通り、型については、decimalにしたいと思います。

    2014年2月3日 9:18
  • 仲澤さん 返信ありがとうございます。

    無関係という点については、すいません。まだ、このコメントによってそういう意見を知った所までで、仕様までは確認できていません。そのため、曖昧な表現にしてしまいました。気を悪くされたなら申し訳ないです。

    指摘頂いている1,2についてはその通りだと考えます。ただ、知識が足りず曖昧な理解で困っています。

    1については、メソッドなどを一度も通さず、中間出力結果なしにやるほうが精度はよいのでしょうか?

    単純な計算、例えば ATM'=現在の気圧/標準気圧のような割り切れないものを用意すれば、やはり0.33333.....のような割り切れない計算値は非常に小さなものですが有効桁までで落ちてしまうので、最終式にまとめた方がよいとなるのでしょうか。

    掛け算だけの途中式や X含有量=質量*理論含有率/100くらいは、情報落ちしないだろうということで、途中式にして良いと考えています。


    • 編集済み ichiethel 2014年2月3日 10:05
    2014年2月3日 10:04
  • インラインを考えた理由としては、後からソースが若干見やすくなるか、と考えました。単に、関数化するくらいならinlineのほうが式を展開する分適切なのではないか、というような理由でもありました。

    まず質問者さんの言われる「インライン」とは何でしょうか? 一般に言われるインラインとは別のものを指しているようでこれでは他者と意思疎通が不可能です。

    ところで実行したい計算は四則演算だけでしょうか? 三角関数や指数関数などは使わないものなのでしょうか? これらはdouble精度のメンバーしかありませんし、さらにCPUの精度に依存します。ですのでこれらの値を含む場合はdecimalで計算しても精度が出ることはありません。

    2014年2月3日 10:48
  • 外池と申します。「理化学」の意味がわかりかねますが・・・、物理や化学のための数値解析は、概ね、微分、積分などの計算、行列計算を応用した多元連立方程式を解く計算などに帰着されると思います。このような数値解析を精度も含めて解説した書物が多く出版されていますので、まず、そちらを勉強されることをお勧めします。

    さらに、典型的な計算手法の具体的なコーディングを示した解説書も出ていますので、そういうものを参考にしてプログラムして、さらに、ご自身でベンチマーク計算をやって精度を確認されるのがよろしいかと。

    小生のお勧めはNumerical Recipesです。
    http://www.nr.com/

    佐祐理さんがご指摘になっているとおり、FPUに搭載されている三角関数、指数関数、対数関数等を使うのであれば、結局のところ、double型で計算を組み立てるしかありません。で、小生が知る範囲では、大抵の場合、十分な精度が得られます。

    Excelと全ての桁を一致させよう、という目標設定は、本質的とは思えません。


    • 編集済み 外池 2014年2月3日 13:05
    2014年2月3日 12:01
  • 佐祐理さんがご指摘になっているとおり、FPUに搭載されている三角関数、指数関数、対数関数等を使うのであれば、結局のところ、double型で計算を組み立てるしかありません。
    ちょっと説明が言葉足らずだったかもしれませんが、C#で計算する以上、Math.Sin()などのメソッドを使用するしかありませんが、そこで用意されているオーバーロードがdoubleのみということを伝えたかったのです。
    # まさか多項式展開とかして目標精度まで計算するというわけでもないでしょうし。
    2014年2月4日 0:51
  • 佐祐理さん 返信ありがとうございます。

    インライン関数について、仰られているとおり自分に間違いがあったようです。

    仲澤さんのコメントより
    >インライン関数等の「インライン」の指定、
    >すなわち 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の精度に依存するというのも知らなかったです。

    午後にもう少し時間が持てそうなので、もうちょっと調べてみたいと思います。

    2014年2月4日 1:38
  • 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
    2014年2月4日 2:20
  • 外池です。小生も言葉足らずでした。組み込みの三角関数等を使う場合、どの言語でも大抵の場合、FPUの関数呼び出しになるだろうな、という推定、さらに、FPUの関数の精度がせいぜいdouble程度という理解が前提にありました。なので「FPU」とわざわざ表記した次第。

    以下、蛇足。(無視してもらった方が良いかも。)

    inoguchiさんが、四則演算のみで計算を組み立てられる(ホントに大丈夫かな? 平方根もなし?)ということなので、整数を二つ組み合わせて、有理数として内部表現を作るのも可能かな? とは思います。

    2014年2月4日 4:28
  • 今さらながらの蛇足レスですが、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 ***

    2014年2月4日 7:26
  • それの効果については近々時代遅れになる予定で、Visual C++ Compiler November 2013 CTPから対応するconstexprを使うことになるでしょう。
    # こんな感じでコンパイル時定数として保証されます。 http://ideone.com/opkoM8
    2014年2月4日 7:38
  • 佐祐理さん SuferOnWwwさん 返信ありがとうございます。

    constexprなんてのがC++はあるんですね。

    実際にプログラムを書いて計算させてみたところ、十分な精度がでることがわかりました。個人的にも非常に勉強なってとてもよかったです。

    ありがとうございました!

    2014年2月4日 9:40
  • 計算誤差を少なくすることが目的なのでしょうか?それともC#でプログラムを書くことが目的なのでしょうか?

    前者に重きを置くなら他の言語&ライブラリ(Ruby&bigdecimalなど)を使うことを考えた方が良いのではないでしょうか。


    library bigdecimal:

    http://docs.ruby-lang.org/ja/2.1.0/library/bigdecimal.html


    2014年2月4日 13:53