Math.Pow()是如何在.NET框架中实现的?

我正在寻找一个有效的方法来计算__abc3(说a = 2b = 50)。首先,我决定看一下Math.Pow()函数的实现。但在. net Reflector中,我所发现的是:

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

当我调用Math.Pow()函数时,我可以看到里面发生了什么,其中有哪些资源?

50990 次浏览

如果pow的免费C版本是任何指示,它看起来不像你所期望的任何东西。找到。net版本对你没有太大帮助,因为你要解决的问题(即有整数的问题)简单几个数量级,可以在几行c#代码用平方求幂算法中解决。

MethodImplOptions.InternalCall

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

查看代码需要CLR的源代码。你可以从SSCLI20分布。它是在。net 2.0的时候编写的,我发现底层实现,比如Math.Pow()对于CLR的后期版本仍然是非常准确的。

查找表位于clr/src/vm/ call.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()上碰壁,微软从英特尔那里购买了该代码。要比英特尔的工程师做得更好是不可能的。尽管我用高中课本的身份识别速度快了一倍:

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

但不是一个真正的替代品,因为它会从3个浮点运算中累积错误,并且不能处理Pow()所具有的奇怪的域问题。比如0^0和-∞的任意次方。

__abc2很好,但我想补充一点,如果b是整数,那么用二进制分解可以非常有效地计算a^b。下面是Henry Warren 黑客的喜悦的修改版本:

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-Hard问题。基本上这就意味着二值分解已经很好了。

通过这些答案,我学到了很多幕后计算: 我已经在一个有广泛测试覆盖用例的编码平台上尝试了一些变通方法,并找到了一个非常有效的方法(解决方案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;
*/
}

Leetcode接受的答案:

public class Solution {
public double MyPow(double x, int n) {
if(n==0) return 1;
        

long abs = Math.Abs((long)n);
        

var result = pow(x, abs);
            

return n > 0 ? result : 1/result;
}
    

double pow(double x, long n){
if(n == 1) return x;
        

var result = pow(x, n/2);
result = result * result * (n%2 == 1? x : 1);
return result;
}
}