小编典典

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

all

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

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

Math.Pow()当我调用函数时,我可以看到哪些资源在里面发生了什么?


阅读 108

收藏
2022-03-17

共1个答案

小编典典

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().

唯一有趣的其他实现细节是表中的 FCIntrinsic
宏。这暗示抖动可能将该功能实现为内在函数。换句话说,用浮点机器代码指令替换函数调用。情况并非如此Pow(),它没有 FPU
指令。但肯定适用于其他简单的操作。值得注意的是,这可以使 C# 中的浮点数学比 C++
中的相同代码快得多,请检查此答案以了解原因:
我会保持简短,它已经被标记为已回答。C# 具有定义良好的浮点模型的巨大优势。这恰好与 x86 和 x64 处理器上 FPU 和 SSE 指令集的本机操作模式相匹配。那里没有巧合。JITter 将 Math.Sqrt() 编译为一些内联指令。

本机 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 提升到任何幂一样。

2022-03-17