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

412

Я искал эффективный подход для вычисления 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()?

  • 15
    Точно так же, как к вашему сведению, если вы запутались во всем InternalCall с модификатором extern (так как они кажутся конфликтующими), пожалуйста, посмотрите вопрос (и полученные ответы), который я опубликовал об этой самой вещи.
  • 6
    Для операции 2^x если x является целым числом, результатом является операция сдвига. Поэтому, возможно, вы могли бы построить результат, используя мантиссу 2 и показатель степени x .
Показать ещё 2 комментария
Теги:
pow

3 ответа

771
Лучший ответ

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, поднятые до любой степени.

  • 423
    Отличный ответ, StackOverflow нуждается в большем количестве подобных вещей, а не в «Почему вы хотите это знать?» это случается слишком часто.
  • 2
    Согласна с Томом В. Фантастический ответ. Я узнал больше, чем ожидал. Интересно, что я набрал 1000 баллов, пометив ваш пост как «Ответ». Еще раз спасибо.
Показать ещё 15 комментариев
100

Ответ 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. Таким образом, в основном это означает, что двоичное разложение так же хорошо, как и получается.

  • 11
    Этот алгоритм ( квадрат и умножение ) также применяется, если a является числом с плавающей запятой.
  • 13
    На практике это можно сделать немного лучше, чем родное квадратичное и умножение. Например, подготовка справочных таблиц для малых показателей, чтобы вы могли возводить в квадрат несколько раз и только затем умножение, или создание оптимизированных цепочек сложения квадратов для фиксированных показателей. Проблема такого рода является неотъемлемой частью важных криптографических алгоритмов, поэтому было проведено немало работы по ее оптимизации. NP-твердость касается только асимптотики в худшем случае , мы часто можем предложить оптимальные или почти-оптимальные решения для случаев, возникающих на практике.
Показать ещё 1 комментарий
62

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

  • 0
    Спасибо за Ваш ответ. Первая ссылка удивила меня, так как я не ожидал такой масштабной технической реализации функции Pow (). Хотя ответ Ханса Пассанта подтверждает, что в мире .Net то же самое. Я думаю, что могу решить проблему, используя некоторые методы, перечисленные в ссылке алгоритма возведения в квадрат. Еще раз спасибо.
  • 2
    Я не верю, что этот код эффективен. 30 локальных переменных просто должны поднять все регистры. Я только предполагаю, что это версия ARM, но на x86 30 локальных переменных в методе - это круто.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню