Math.Pow()是如何在.NET Framework中实现的
本文关键字:Framework NET 实现 Pow Math | 更新日期: 2023-09-27 18:20:16
我正在寻找一种有效的方法来计算ab(比如a = 2
和b = 50
)。首先,我决定研究一下Math.Pow()
函数的实现。但在.NET Reflector中,我发现的只是:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);
当我调用Math.Pow()
函数时,我可以从哪些资源中看到内部发生了什么?
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/foat/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和-无限提升到任何力量。
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.对于除了广泛搜索之外的任何b,对于寻找因子的最优序列来计算a^b
的一般问题,也没有已知的解决方案。这是一个NP难题。所以基本上,这意味着二进制分解是最好的。
如果免费提供的pow
的C版本有任何指示,那么它看起来并不像您所期望的那样。找到.NET版本对您没有多大帮助,因为您正在解决的问题(即带整数的问题)简单了几个数量级,并且可以通过平方取幂算法在几行C#代码中解决。
通过这些答案,了解了许多关于幕后计算的知识:我在一个有大量测试覆盖案例的编码平台上尝试了一些变通方法,并找到了一种非常有效的方法(解决方案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;
}
}