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

Paw*_*hra 427 .net c# pow

我一直在寻找用于计算的有效方法b(说a = 2b = 50).为了开始,我决定看一下Math.Pow()函数的实现.但在.NET Reflector中,我发现的只有:

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);
Run Code Online (Sandbox Code Playgroud)

当我调用Math.Pow()函数时,我可以看到内部发生了什么的一些资源?

Han*_*ant 851

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()
Run Code Online (Sandbox Code Playgroud)

搜索"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));
}
Run Code Online (Sandbox Code Playgroud)

但不是真正的替代品,因为它累积了来自3个浮点运算的错误,并且没有处理Pow()所具有的怪异域问题.像0 ^ 0和-Infinity提升到任何功率.

  • 很棒的答案,StackOverflow需要更多这类东西,而不是"为什么你想知道这一点?" 这种情况经常发生. (436认同)
  • @Blue - 我不知道,不是取笑英特尔工程师.我的高中书确实有一个问题,就是提出一个负面积分的力量.Pow(x,-2)是完全可计算的,Pow(x,-2.1)是未定义的.域问题是一个难以对付的问题. (16认同)
  • @ BlueRaja-DannyPflughoeft:花了很多精力来确保浮点运算尽可能接近正确舍入的值.众所周知,"pow"很难准确地实现,是一种超越功能(参见[Table-Maker's Dilemma](http://en.wikipedia.org/wiki/Rounding#The_table-maker.27s_dilemma)).使用完整的电源可以轻松实现. (12认同)
  • @Hans Passant:为什么Pow(x,-2.1)未定义?对于所有x和y,数学上都定义了pow.你确实倾向于得到负x和非整数y的复数. (9认同)
  • @Jules pow(0,0)未定义. (8认同)
  • @HansPassant - 我认为你的意思是'Pow(x,-2.1)`对于x的*negative*值是未定义的(或复杂的),对吧?除非x为0,否则指数上的符号不会使其更多或更少可计算. (4认同)
  • 同意Tom W.很棒的回答.我学到的东西比我期待的要多.有趣的是,我将你的帖子标记为"已回答",达到了1000分.再次感谢. (2认同)
  • @Hans Ups 我完全错过了功率为双倍的部分。抱歉。 (2认同)
  • @Jules复杂数字刚刚添加到Framework for 4.0,http://msdn.microsoft.com/en-us/library/system.numerics.complex(VS.100).aspx.它现在定义了Pow(Complex,Double)和Pow(Complex,Complex). (2认同)
  • @amoss他们呢?五年前,当我在桌面应用程序中编写用于DIY平铺的地理空间功能时,这本来是非常方便的.值得庆幸的是,在我发疯之前谷歌已经过时了.茜草. (2认同)

Mic*_*zyk 108

Hans Passant的答案很棒,但我想补充一点,如果b是一个整数,那么a^b可以通过二进制分解非常有效地计算.这是Henry Warren的Hacker's Delight的修改版本:

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;
    }    
}
Run Code Online (Sandbox Code Playgroud)

他指出,对于所有b <15,这个操作是最优的(算术或逻辑运算的最小数量).对于a^b除了广泛的b之外的任何b ,找到计算最佳因子序列的一般问题也没有已知的解决方案.搜索.这是NP难问题.所以基本上这意味着二进制分解和它一样好.

  • 在实践中,它可能比原生的square-and-multiply做得更好.例如,为小指数准备查找表,以便您可以平方几次,然后才能相乘,或者为固定指数构建优化的平方加法链.这种问题是重要加密算法的组成部分,因此在优化加密算法方面做了大量工作.NP硬度仅约为*最坏情况渐近*,我们经常可以为实际出现的问题实例提供最优或近似最优解. (14认同)
  • 如果`a`是浮点数,则该算法([square and multiply](http://en.wikipedia.org/wiki/Exponentiation_by_squaring))也适用. (11认同)

das*_*ght 69

如果免费提供C版本的pow任何指示,它看起来不像你期望的任何东西.它不会是太大的帮助您找到.NET版本,因为你要解决(即一个与整数)的问题是大小简单的订单,可以在C#几行代码来解决与幂通过平方算法.

  • 我不相信这段代码是有效的.30个局部变量应该碰到所有寄存器.我只假设它是ARM版本,但在x86 30方法中的局部变量非常棒. (2认同)