web-dev-qa-db-ja.com

Math.Pow()は.NET Frameworkでどのように実装されていますか?

を計算するための効率的なアプローチを探していましたb (a = 2b = 50など)。物事を開始するために、私はMath.Pow()関数の実装を見ることにしました。しかし、 。NET Reflector では、私が見つけたのはこれだけです:

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);

Math.Pow() functionを呼び出したときに内部で何が起こっているかを確認できるリソースにはどのようなものがありますか?

421
Pawan Mishra

MethodImplOptions.InternalCall

つまり、メソッドは実際にC++で記述されたCLRで実装されます。ジャストインタイムコンパイラは、内部的に実装されたメソッドを含むテーブルを参照し、C++関数の呼び出しを直接コンパイルします。

コードを見るには、CLRのソースコードが必要です。 SSCLI20ディストリビューション から取得できます。 .NET 2.0の時間枠を中心に書かれており、Math.Pow()のような低レベルの実装は、CLRの以降のバージョンでも大部分が正確であることがわかりました。

ルックアップテーブルはclr/src/vm/ecall.cppにあります。 Math.Pow()に関連するセクションは次のようになります。

FCFuncStart(gMathFuncs)
    FCIntrinsic("Sin", COMDouble::Sin, CORINFO_INTRINSIC_Sin)
    FCIntrinsic("Cos", COMDouble::Cos, CORINFO_INTRINSIC_Cos)
    FCIntrinsic("Sqrt", COMDouble::Sqrt, CORINFO_INTRINSIC_Sqrt)
    FCIntrinsic("Round", COMDouble::Round, CORINFO_INTRINSIC_Round)
    FCIntrinsicSig("Abs", &gsig_SM_Flt_RetFlt, COMDouble::AbsFlt, CORINFO_INTRINSIC_Abs)
    FCIntrinsicSig("Abs", &gsig_SM_Dbl_RetDbl, COMDouble::AbsDbl, CORINFO_INTRINSIC_Abs)
    FCFuncElement("Exp", COMDouble::Exp)
    FCFuncElement("Pow", COMDouble::Pow)
    // etc..
FCFuncEnd()

「COMDouble」を検索すると、clr/src/classlibnative/float/comfloat.cppに移動します。私はあなたにコードをspareしまない、あなた自身を見てみなさい。基本的に、コーナーケースをチェックし、CRTのバージョンのpow()を呼び出します。

他の興味深い実装の詳細は、表のFCIntrinsicマクロのみです。これは、ジッターが関数を組み込み関数として実装する可能性があることのヒントです。つまり、関数呼び出しを浮動小数点マシンコード命令に置き換えます。 Pow()には当てはまりませんが、FPU命令はありません。しかし、他の簡単な操作については確かです。注目すべきは、これによりC#の浮動小数点演算がC++の同じコードよりも大幅に高速化できることです。理由は this answer を確認してください。

ところで、Visual Studioのvc/crt/srcディレクトリのフルバージョンを使用している場合、CRTのソースコードも利用できます。 pow()で壁にぶつかるでしょうが、MicrosoftはそのコードをIntelから購入しました。 Intelのエンジニアよりも良い仕事をすることはまずありません。私の高校の本の身元は、試してみると2倍の速さでしたが:

public static double FasterPow(double x, double y) {
    return Math.Exp(y * Math.Log(x));
}

しかし、3つの浮動小数点演算からエラーを蓄積し、Pow()が持つ奇妙なドメインの問題を処理しないため、真の代替ではありません。 0 ^ 0および-Infinityが任意の累乗になります。

845
Hans Passant

ハンスパッサントの答え は素晴らしいですが、bが整数の場合、a^bはバイナリ分解で非常に効率的に計算できます。これは、ヘンリーウォーレンのハッカーの喜びの修正版です。

public static int iexp(int a, uint b) {
    int y = 1;

    while(true) {
        if ((b & 1) != 0) y = a*y;
        b = b >> 1;
        if (b == 0) return y;
        a *= a;
    }    
}

彼は、この操作はすべてのb <15に対して最適である(最小数の算術演算または論理演算を行う)ことを指摘しています。また、最適な因子のシーケンスを見つけてa^bをb広範な検索。これはNPハードの問題です。つまり、基本的には、バイナリ分解が得られるのと同じくらい良いことを意味します。

105
Michael Graczyk

powの自由に利用可能なCバージョン が何らかの兆候である場合、それはあなたが期待するもののようには見えません。 .NETバージョンを見つけることはあまり役に立ちません。なぜなら、あなたが解決しようとしている問題(すなわち、整数の問題)は桁違いに簡単で、C#コードの数行で解決できるからです 二乗アルゴリズムによるべき乗を使用

66
dasblinkenlight