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


432

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

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

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


15
参考までに、全体について修飾子InternalCallと混同している場合extern(競合しているように思われる場合)、これと同じことについて私が投稿した質問(および結果の回答)を参照しください。
CraigTP 2012年

6
ため2^xなら演算x結果はシフト操作される整数です。したがって、仮数部2と指数部を使用して結果を作成することができますx
ja72 2012

@SurajJainあなたのコメントは、実際には個別に投稿する必要がある質問です。
ja72 2016年

@SurajJain私はあなたに同意します。私はモデレーターではないので、ここではあまりできません。たぶんdownvoteの質問がで求められることができ meta.stackoverflow.com
ja72

回答:


854

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に移動します。コードは割愛します。自分の目で見てください。基本的にはコーナーケースをチェックしてから、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を任意のパワーに引き上げたようなものです。


437
すばらしい答えです。StackOverflowは、「なぜそれを知りたいのか」ではなく、この種のことをもっと必要とします。それはあまりにも頻繁に起こります。
トムW

16
@Blue-わからない、Intelエンジニアをからかうのは短い。私の高校の本には、何かを負の積分の力に引き上げる問題があります。Pow(x、-2)は完全に計算可能であり、Pow(x、-2.1)は未定義です。ドメインの問題は対処するのが難しいです。
ハンスパッサント2012年

12
@ BlueRaja-DannyPflughoeft:浮動小数点演算が正しく丸められた値にできるだけ近づくようにするために、多くの努力が費やされます。pow超越関数であるため、正確に実装することはよく知られています(Table-Makerのジレンマを参照)。統合されたパワーにより、はるかに簡単です。
ポルジュ2012年

9
@Hans Passant:Pow(x、-2.1)が未定義になるのはなぜですか?数学的にpowはすべてのxとyに対してどこでも定義されます。負のxと非整数yの複素数を取得する傾向があります。
Jules

8
@Jules pow(0、0)は定義されていません。
エンボス

110

ハンス・パッサントの答えは素晴らしいです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ハード問題です。つまり、基本的には、バイナリ分解が可能な限り良好であることを意味します。


11
このアルゴリズム(square and multiplyaは、が浮動小数点数の場合にも適用されます。
CodesInChaos 2012

14
実際には、ネイティブの二乗乗算よりもかなり優れた処理を行うことができます。たとえば、小さな指数のルックアップテーブルを準備して、数回平方してから乗算できるようにしたり、固定指数用に最適化された二乗加算チェーンを構築したりします。この種の問題は重要な暗号化アルゴリズムに不可欠であるため、最適化にはかなりの労力が費やされてきました。NPの硬度は最悪の場合の漸近的なものにすぎません。実際に発生する問題のインスタンスに対して、最適またはほぼ最適なソリューションを生成できることがよくあります。
CodesInChaos 2013年

テキストはa整数であることを述べていませんが、コードは整数です。その結果として、私はテキストの「非常に効率的な」計算の結果の正確さについて疑問に思います。
アンドリューモートン

69

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


ご回答有難うございます。最初のリンクは、Pow()関数のそのような大規模な技術的実装を期待していなかったので、私を驚かせました。ハンスパッサントの回答は、.Netの世界でも同じであることを確認しています。二乗アルゴリズムのリンクにリストされているいくつかの手法を利用することで、当面の問題を解決できると思います。再度、感謝します。
Pawan Mishra 2012年

2
このコードが効率的であるとは思いません。30個のローカル変数は、すべてのレジスターをバンプします。ARMバージョンだと思いますが、x86では、メソッドのローカル変数は素晴らしいです。
Alex Zhukovskiy、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.