Как Math.Pow () реализован в .NET Framework? - PullRequest
420 голосов
/ 15 января 2012

Я искал эффективный подход для вычисления b (скажем, a = 2 и b = 50).Для начала я решил взглянуть на реализацию функции Math.Pow().Но в .NET Reflector все, что я обнаружил, было так:

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

Какие ресурсы я могу увидеть, что происходит внутри, когда я вызываю функцию Math.Pow()?

Ответы [ 3 ]

844 голосов
/ 15 января 2012

MethodImplOptions.InternalCall

Это означает, что метод фактически реализован в CLR, написанном на C ++. Компилятор Just-in-Time обращается к таблице с внутренне реализованными методами и напрямую компилирует вызов функции 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 ++, проверьте этот ответ по причине.

Кстати, исходный код CRT также доступен, если у вас есть полная версия каталога Visual Studio vc / crt / src. Но вы все равно наберете 1025 *, Microsoft приобрела этот код у Intel. Делать лучше, чем инженеры Intel, вряд ли. Хотя моя книга о старшей школе была в два раза быстрее, когда я попробовал:

public static double FasterPow(double x, double y) {
    return Math.Exp(y * Math.Log(x));
}

Но не является истинной заменой, потому что он накапливает ошибку от 3 операций с плавающей запятой и не решает проблемы домена, которые есть у Pow (). Как 0 ^ 0 и -Infinity, возведенные в любую степень.

105 голосов
/ 10 июля 2012

Ответ Ханса Пассанта великолепен, но я хотел бы добавить, что если b является целым числом, то a^b может быть очень эффективно вычислено с помощью двоичной декомпозиции.Вот модифицированная версия от 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;
    }    
}

Он отмечает, что эта операция является оптимальной (выполняет минимальное количество арифметических или логических операций) для всех b <15.не существует известного решения общей проблемы поиска оптимальной последовательности факторов для вычисления <code>a^b для любого b, кроме расширенного поиска.Это проблема NP-Hard.Таким образом, в основном это означает, что двоичная декомпозиция настолько хороша, насколько это возможно.

66 голосов
/ 15 января 2012

Если свободно доступная C-версия pow является какой-либо индикацией, это не похоже на то, что вы ожидаете. Вам не очень поможет найти версию .NET, потому что проблема, которую вы решаете (т. Е. С целыми числами), на несколько порядков проще и может быть решена в несколько строк кода C # с возведением в степень по алгоритму возведения в квадрат .

...