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


432

我一直在寻找一种计算b的有效方法(例如a = 2b = 50)。首先,我决定看一下Math.Pow()函数的实现。但是在.NET Reflector中,我发现的只是:

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

调用Math.Pow()函数时,我能看到哪些内部资源?


15
正如一个供参考,如果你感到困惑整个InternalCallextern改性剂(因为他们似乎要发生冲突),请参阅问题(以及所产生的答案),我张贴了关于这个同样的事情。
CraigTP

6
对于一个2^x运算,如果x为整数,则结果为移位运算。因此,也许您可​​以使用的尾数2和的指数构造结果x
ja72 2012年

@SurajJain您的评论实际上是您需要单独发布的问题。
2016年

@SurajJain我同意你的观点。我不是主持人,所以我在这里不能做太多事情。也许可以在meta.stackoverflow.com
ja72

Answers:


854

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 / 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和-Infinity升到任何幂。


437
很好的答案,StackOverflow需要更多这类东西,而不是“为什么要知道?” 这种情况经常发生。
汤姆W

16
@Blue-我不知道,距离嘲笑英特尔工程师还很短。我的高中书确实在将某些东西提升为负积分的能力上存在问题。Pow(x,-2)是完全可计算的,Pow(x,-2.1)是未定义的。域问题非常棘手。
汉斯·帕桑

12
@ BlueRaja-DannyPflughoeft:为确保浮点运算尽可能接近正确取整的值而花费了大量精力。pow众所周知,作为先验功能,要实现它非常困难(请参阅Table-Maker's Dilemma)。使用集成的功能要容易得多。
porges 2012年

9
@Hans Passant:为什么Pow(x,-2.1)是不确定的?数学上,对于所有x和y都定义了功率。对于负数x和非整数y,您确实会得到复数。
Jules

8
未定义@Jules pow(0,0)。
浮雕

110

汉斯·帕桑(Hans Passant)的回答很好,但我想补充一点,如果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,还没有已知的解决方案来查找的因素的最优序列计算的一般问题a^b比广泛其他任何B-搜索。这是一个NP难题。因此,从根本上讲,这意味着二元分解也将尽其所能。


11
如果是浮点数,则也适用此算法(平方和乘a
CodesInChaos 2012年

14
在实践中,做得比本机平方乘好得多。例如,为小指数准备查找表,以便您可以平方几次然后才可以相乘,或者为固定指数构建优化的平方加法链。这种问题对于重要的密码算法是必不可少的,因此,在优化它方面有大量工作要做。NP硬度仅是最坏情况下的渐近线,对于实践中出现的问题,我们经常可以提供最佳或接近最佳的解决方案。
CodesInChaos

文本没有提到a是整数,但是代码提到了。结果,我想知道文本“非常有效”的计算结果的准确性
安德鲁·莫顿

69

如果可以免费获得C版本,pow则表明它与您期望的不一样。找到.NET版本对您没有多大帮助,因为您要解决的问题(即带整数的问题)要简单几个数量级,并且可以用几行C#代码通过幂运算来解决。通过平方算法


感谢您的回答。第一个链接使我感到惊讶,因为我没想到Pow()函数会实现如此大规模的技术实现。尽管Hans Passant的答案也证实了它在.Net世界中的相同性。我想我可以利用平方算法链接中列出的一些技术来解决当前的问题。再次感谢。
Pawan Mishra 2012年

2
我不认为此代码有效。30个局部变量应该会碰到所有寄存器。我只认为它是ARM版本,但是在x86上,该方法中的30个局部变量很棒。
Alex Zhukovskiy 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.