Я искал эффективный подход для вычисления 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()
?
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, поднятые до любой степени.
Ответ 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. Таким образом, в основном это означает, что двоичное разложение так же хорошо, как и получается.
a
является числом с плавающей запятой.
Если свободно доступная версия C pow
- это любое указание, оно не похоже на то, что вы ожидаете. Это не помогло бы вам найти версию .NET, потому что проблема, которую вы решаете (т.е. С целыми числами), упрощает порядки величин и может быть решена в нескольких строках кода С# с возведением в степень с помощью алгоритма возведения в квадрат.
InternalCall
с модификаторомextern
(так как они кажутся конфликтующими), пожалуйста, посмотрите вопрос (и полученные ответы), который я опубликовал об этой самой вещи.2^x
еслиx
является целым числом, результатом является операция сдвига. Поэтому, возможно, вы могли бы построить результат, используя мантиссу2
и показатель степениx
.