Як Math.Pow () реалізується в .NET Framework?


432

Я шукав ефективний підхід для обчислення 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
Як FYI, якщо ви плутаєтесь у цілому InternalCallз externмодифікатором (оскільки вони здаються конфліктними), будь ласка, подивіться на питання (та отримані відповіді), яке я опублікував щодо цього самого.
CraigTP

6
Для 2^xоперації, якщо xціле число, результатом є операція зсуву. Тож, можливо, ви могли б сконструювати результат, використовуючи мантісу 2та показник x.
ja72

@SurajJain ваш коментар - це фактично питання, яке потрібно опублікувати окремо.
ja72

@SurajJain Я з вами згоден. Я не модератор, тому не можу багато чого зробити тут. Можливо, питання про скорочення можна задати
ja72

Відповіді:


854

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()цього не існує інструкції ФПУ. Але звичайно для інших простих операцій. Примітно те, що це може зробити математику з плаваючою комою в C # значно швидшою, ніж той самий код у C ++, перевірте цю відповідь з причини.

До речі, вихідний код для 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));
}

Але це не справжній замінник, оскільки він накопичує помилку в 3 операціях з плаваючою точкою і не справляється з проблемами домену weirdo, які мають Pow (). Як 0 ^ 0 і -Безмежність піднята до будь-якої сили


437
Чудова відповідь, StackOverflow потребує більше подібних речей, а не "Чому ви хочете це знати?" це відбувається занадто часто.
Том Ш

16
@Blue - Я не знаю, якщо не насміхатися з інженерів Intel. У моїй середній школі є проблема підняти щось до сили негативного інтеграла. Pow (x, -2) ідеально обчислюється, Pow (x, -2.1) не визначений. Проблеми з доменом - це сука для вирішення.
Ганс Пасант

12
@ BlueRaja-DannyPflughoeft: Багато зусиль витрачається на те, щоб операції з плаваючою комою були максимально наближені до значення, правильно округленого. powсумно важко точно реалізувати, будучи трансцендентною функцією (див. Дилему таблиці-виробника таблиці ). Набагато простіше з цілісною потужністю.
porges

9
@Hans Passant: Чому Pow (x, -2.1) не буде визначеним? Математично Pow визначається скрізь для всіх x і y. Ви маєте тенденцію отримувати складні числа для від’ємного х та не цілого числа y.
Жуль

8
@Jules pow (0, 0) не визначено.
тиснення

110

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

14
На практиці це можливо зробити трохи краще, ніж рідні квадратні та множинні. Наприклад, підготовка таблиць пошуку для невеликих експонентів, щоб ви могли квадратирувати кілька разів і лише потім множити або будувати оптимізовані ланцюги додавання квадратів для фіксованих показників. Така проблема є невід'ємною частиною важливих криптографічних алгоритмів, тому над її оптимізацією було проведено досить багато роботи. Твердість НП стосується лише найгірших асимптотиків , ми часто можемо створити оптимальні або майже оптимальні рішення для випадків, що виникають на практиці.
CodesInChaos

У тексті не згадується aціле число, але код є. Як наслідок цього, мені цікаво точність результату "дуже ефективного" обчислення тексту.
Ендрю Мортон

69

Якщо у вільно доступній версії Cpow є будь-яка ознака, вона не схожа на те, що ви очікували. Вам було б не дуже допомогти знайти версію .NET, тому що проблема, яку ви вирішуєте (тобто та, яка має цілі числа), на замовлення величин простіша, і її можна вирішити в декількох рядках коду C # за допомогою експоненції алгоритмом квадратування .


Дякую за вашу відповідь. Перше посилання мене здивувало, оскільки я не очікував такої масової технічної реалізації функції Pow (). Хоча відповідь Ганса Пасанта підтверджує, що це те саме в світі .Net. Я думаю, що я можу вирішити проблему, використовуючи деяку техніку, перелічену у посиланні алгоритму квадратування. Знову дякую.
Pawan Mishra

2
Я не вірю, що цей код ефективний. 30 локальних змінних просто повинні збивати всі регістри. Я лише гадаю, що це версія ARM, але на x86 30 локальних змінних у методі є дивним.
Олексій Жуковський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.