Как реализован Math.Pow() в .NET Framework?

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

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

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

Ответ 1

MethodImplOptions.InternalCall

Это означает, что метод фактически реализован в CLR, написанном на С++. Компилятор "точно в срок" обращается к таблице с внутренне реализованными методами и напрямую компилирует вызов функции С++.

Взглянув на код, требуется исходный код для 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. Но, конечно же, для других простых операций. Примечательно, что это может сделать математику с плавающей запятой в С# существенно быстрее, чем тот же код в С++, проверьте этот ответ по этой причине.

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

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

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

Ответ 2

Ответ Hans Passant - это замечательно, но я хотел бы добавить, что если b является целым числом, то a^b можно вычислить очень эффективно с бинарным разложением, Здесь измененная версия от Henry Warren Hacker 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. Также нет известного решения общей проблемы нахождения оптимальной последовательности факторов для вычисления a^b для любого b, кроме расширенного поиска. Это проблема NP-Hard. Таким образом, в основном это означает, что двоичное разложение так же хорошо, как и получается.

Ответ 3

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