关于c#:如何在.NET Framework中实现Math.Pow()?

How is Math.Pow() implemented in .NET Framework?

我正在寻找一种计算ab的有效方法(比如a = 2b = 50)。为了开始工作,我决定看看Math.Pow()函数的实现。但在.NET Reflector中,我发现的是:

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

当我调用Math.Pow()函数时,我可以看到哪些资源是内部正在发生的事情?


MethodImplOptions.InternalCall

这意味着该方法实际上是在CLR中实现的,用C++编写。准时编译器使用内部实现的方法咨询一个表,并直接编译调用C++函数。

查看代码需要clr的源代码。你可以从SSCLI20发行版上得到。它是围绕.NET 2.0时间框架编写的,我发现低级的实现,如Math.Pow(),对于更高版本的clr仍然大体上是准确的。

查找表位于clr/src/vm/ecall.cpp中。与Math.Pow()相关的部分如下:

1
2
3
4
5
6
7
8
9
10
11
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()

唯一有趣的其他实现细节是表中的fcinherin宏。这是一个提示,即抖动可能将函数作为内部函数实现。换句话说,用浮点机器代码指令替换函数调用。pow()不是这样的,它没有FPU指令。但对于其他简单的操作当然是如此。值得注意的是,这可以使C语言中的浮点运算基本上比C++中的相同代码快,检查这个答案的原因。

顺便说一下,如果您有完整版本的Visual Studio VC/CRT/SRC目录,那么CRT的源代码也可用。不过,微软从英特尔那里购买了这段代码,这对pow()来说是个打击。比英特尔工程师做得更好是不太可能的。尽管我读高中时的身份是我读高中时的两倍:

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

但不是一个真正的替代,因为它从3个浮点运算中积累了错误,并且不处理pow()所具有的奇怪域问题。就像0^0和-infinity被提升到任何幂。


汉斯·帕桑特的回答很好,但我想补充一点,如果b是一个整数,那么a^b可以用二进制分解非常有效地计算出来。以下是亨利·沃伦的《黑客之乐》的修改版本:

1
2
3
4
5
6
7
8
9
10
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的情况,这个操作是最优的(做算术或逻辑操作的最小数目)。此外,除了广泛搜索外,还没有已知的一般问题的解决方案,即找到一个最佳因子序列来计算任何B的a^b。这是个NP难题。所以基本上这意味着二元分解是尽可能好的。


如果免费提供的pow的C版本有任何指示,它看起来不像您期望的那样。找到.NET版本对您没有多大帮助,因为您要解决的问题(即具有整数的问题)是更简单的数量级,并且可以通过平方算法的求幂在几行C代码中解决。