我正在寻找一种计算ab的有效方法(例如a = 2和b = 50)。首先,我决定看一下Math.Pow()函数的实现。但是在.NET Reflector中,我发现的是:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);
当我调用Math.Pow()函数时,我可以看到其中的一些资源是什么?
我正在寻找一种计算ab的有效方法(例如a = 2和b = 50)。首先,我决定看一下Math.Pow()函数的实现。但是在.NET Reflector中,我发现的是:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);
当我调用Math.Pow()函数时,我可以看到其中的一些资源是什么?
当前回答
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;
}
}
其他回答
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()。
The only other implementation detail that's interesting is the FCIntrinsic macro in the table. That's a hint that the jitter may implement the function as an intrinsic. In other words, substitute the function call with a floating point machine code instruction. Which is not the case for Pow(), there is no FPU instruction for it. But certainly for the other simple operations. Notable is that this can make floating point math in C# substantially faster than the same code in C++, check this answer for the reason why.
顺便说一下,如果你有完整版的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-Hard问题。基本上这就意味着二值分解已经很好了。
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;
}
}
通过这些答案,我学到了很多幕后计算: 我在一个有广泛测试覆盖用例的编码平台上尝试了一些变通方法,并找到了一个非常有效的方法(解决方案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;
*/
}
如果可以免费获得的pow C版本有任何指示性的话,那么它看起来并不像您所期望的那样。找到。net版本对您没有太大帮助,因为您正在解决的问题(即整数问题)要简单几个数量级,并且可以用几行c#代码用平方算法求幂来解决。