444

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

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

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

4

4 に答える 4

872

MethodImplOptions.InternalCall

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

コードを確認するには、CLRのソースコードが必要です。SSCLI20ディストリビューションから入手できます。これは.NET2.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に移動します。私はあなたにコードを惜しまないでしょう、ただあなた自身を見てください。基本的にコーナーケースをチェックしてから、CRTのバージョンのを呼び出しますpow()

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

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

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

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

于 2012-01-15T14:52:59.350 に答える
115

Hans Passantの答えbは素晴らしいですが、が整数の場合、 a^b2進分解で非常に効率的に計算できることを付け加えたいと思います。これがヘンリーウォーレンのハッカーのたのしみからの修正版です:

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。探す。これはNP困難な問題です。つまり、基本的には、バイナリ分解が可能な限り優れていることを意味します。

于 2012-07-10T18:15:34.000 に答える
71

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

于 2012-01-15T14:43:49.050 に答える
0

答えを調べて、計算の舞台裏について多くのことを学びました。広範なテストカバレッジケースがあるコーディングプラットフォームでいくつかの回避策を試し、それを行うための非常に効果的な方法を見つけました(ソリューション3):

public double MyPow(double x, int n) {
    double res = 1;
    /* Solution 1: iterative : TLE(Time Limit Exceeded)
    double res = 1;
    var len = n > 0 ? n : -n;
    for(var i = 0; i < len; ++i)
        res *= x;   
    
    return n > 0 ? res : 1 / res; 
    */
    
    /* Solution 2: recursive => stackoverflow exception
    if(x == 0) return n > 0 ? 0 : 1 / x;
    if(n == 1) return x;
    
    return n > 0 ? x * MyPow(x, n - 1) : (1/x) * MyPow(1/x, -n); 
    */
    
    //Solution 3:
    if (n == 0) return 1;
    
    var half = MyPow(x, n / 2);
    if (n % 2 == 0) 
        return half * half;
    else if (n > 0) 
        return half * half * x;
    else 
        return half * half / x;
    
    /* Solution 4: bitwise=> TLE(Time Limit Exceeded)
    var b = n > 0 ? n : -n;        
    while(true) {
        if ((b & 1) != 0) 
            res *= x;
        
        b = b >> 1;
        
        if (b == 0) break;
        
        x *= x;
    }   
    return n > 0 ? res : 1 / res; 
    */
}
于 2020-07-16T14:49:54.317 に答える