我一直在寻找一种有效的方法来计算 a b(比如a = 2and b = 50)。首先,我决定看一下Math.Pow()函数的实现。但是在.NET Reflector中,我发现的只是:
a = 2
b = 50
Math.Pow()
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical] public static extern double Pow(double x, double y);
Math.Pow()当我调用函数时,我可以看到哪些资源在里面发生了什么?
MethodImplOptions.InternalCall
这意味着该方法实际上是在 CLR 中实现的,用 C 编写。即时编译器使用内部实现的方法查询表,并直接编译对 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().
pow()
唯一有趣的其他实现细节是表中的 FCIntrinsic 宏。这暗示抖动可能将该功能实现为内在函数。换句话说,用浮点机器代码指令替换函数调用。情况并非如此Pow(),它没有 FPU 指令。但肯定适用于其他简单的操作。值得注意的是,这可以使 C# 中的浮点数学比 C++ 中的相同代码快得多,请检查此答案以了解原因: 我会保持简短,它已经被标记为已回答。C# 具有定义良好的浮点模型的巨大优势。这恰好与 x86 和 x64 处理器上 FPU 和 SSE 指令集的本机操作模式相匹配。那里没有巧合。JITter 将 Math.Sqrt() 编译为一些内联指令。
Pow()
本机 C/C++ 背负着多年的向后兼容性。/fp:precise、/fp:fast 和 /fp:strict 编译选项是最明显的。因此,它必须调用实现 sqrt() 的 CRT 函数并检查选定的浮点选项以调整结果。那很慢。
顺便说一句,如果你有完整版的 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 提升到任何幂一样。