Bagaimana Math.Pow () diimplementasikan dalam .NET Framework?

433

Saya mencari pendekatan yang efisien untuk menghitung b (katakan a = 2dan b = 50). Untuk memulai, saya memutuskan untuk melihat implementasi Math.Pow()fungsi. Tetapi dalam .NET Reflector , yang saya temukan adalah ini:

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

Apa saja sumber daya yang dapat saya lihat sebagai apa yang terjadi di dalam ketika saya memanggil Math.Pow()fungsi?

Pawan Mishra
sumber
15
Sama seperti FYI, jika Anda bingung tentang keseluruhan InternalCalldengan externpengubah (karena mereka tampaknya saling bertentangan), silakan lihat pertanyaan (dan jawaban yang dihasilkan) yang saya posting tentang hal yang sama.
CraigTP
6
Untuk 2^xoperasi jika xbilangan bulat hasilnya adalah operasi shift. Jadi mungkin Anda bisa membuat hasilnya menggunakan mantissa 2dan eksponen x.
ja72
@ SurajJain komentar Anda sebenarnya adalah pertanyaan yang harus Anda posting secara terpisah.
ja72
@ SurajJain, saya setuju dengan Anda. Saya bukan moderator jadi saya tidak bisa berbuat banyak di sini. Mungkin pertanyaan downvote dapat ditanyakan di meta.stackoverflow.com
ja72

Jawaban:

855

MethodImplOptions.InternalCall

Itu berarti bahwa metode ini benar-benar diimplementasikan dalam CLR, ditulis dalam C ++. Kompilator just-in-time berkonsultasi dengan tabel dengan metode yang diimplementasikan secara internal dan mengkompilasi panggilan ke fungsi C ++ secara langsung.

Melihat kode membutuhkan kode sumber untuk CLR. Anda bisa mendapatkannya dari distribusi SSCLI20 . Itu ditulis di sekitar kerangka waktu. NET 2.0, saya telah menemukan implementasi tingkat rendah, ingin Math.Pow()tetap sebagian besar akurat untuk versi CLR nanti.

Tabel pencarian terletak di clr / src / vm / ecall.cpp. Bagian yang relevan dengan Math.Pow()terlihat seperti ini:

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()

Mencari "COMDouble" akan membawa Anda ke clr / src / classlibnative / float / comfloat.cpp. Saya akan menghindarkan Anda kodenya, lihat saja sendiri. Ini pada dasarnya memeriksa kasus sudut, lalu memanggil versi CRT untuk pow().

Satu-satunya detail implementasi lain yang menarik adalah makro FCIntrinsic dalam tabel. Itu petunjuk bahwa jitter dapat mengimplementasikan fungsi sebagai intrinsik. Dengan kata lain, gantilah panggilan fungsi dengan instruksi kode mesin titik mengambang. Yang tidak demikian halnya Pow(), tidak ada instruksi FPU untuk itu. Namun yang pasti untuk operasi sederhana lainnya. Yang perlu dicatat adalah bahwa ini dapat membuat matematika floating point dalam C # secara substansial lebih cepat daripada kode yang sama dalam C ++, periksa jawaban ini untuk alasan mengapa.

Omong-omong, kode sumber untuk CRT juga tersedia jika Anda memiliki versi lengkap dari direktori Visual Studio vc / crt / src. Namun, Anda akan menabrak tembok pow(), Microsoft membeli kode itu dari Intel. Melakukan pekerjaan yang lebih baik daripada para insinyur Intel tidak mungkin. Meskipun identitas buku sekolah menengah saya dua kali lebih cepat ketika saya mencobanya:

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

Tapi bukan pengganti yang benar karena mengakumulasi kesalahan dari 3 operasi floating point dan tidak berurusan dengan masalah domain aneh yang dimiliki Pow (). Seperti 0 ^ 0 dan -Infinity dinaikkan ke daya apa pun.

Hans Passant
sumber
437
Jawaban yang bagus, StackOverflow membutuhkan lebih banyak hal semacam ini, alih-alih 'Mengapa Anda ingin tahu itu?' itu terlalu sering terjadi.
Tom W
16
@Blue - Saya tidak tahu, singkat dari mengolok-olok insinyur Intel. Buku SMA saya memang memiliki masalah mengangkat sesuatu ke kekuatan integral negatif. Pow (x, -2) sangat dapat dihitung, Pow (x, -2.1) tidak terdefinisi. Masalah domain adalah masalah yang harus dihadapi.
Hans Passant
12
@ BlueRaja-DannyPflughoeft: Banyak upaya dihabiskan untuk memastikan bahwa operasi floating-point sedekat mungkin dengan nilai yang dibulatkan dengan benar. powsangat sulit untuk diimplementasikan secara akurat, menjadi fungsi transendental (lihat Dilema Table-Maker's ). Jauh lebih mudah dengan kekuatan integral.
porges
9
@Hans Passant: Mengapa Pow (x, -2.1) tidak dapat ditentukan? Secara matematis pow didefinisikan di mana-mana untuk semua x dan y. Anda cenderung mendapatkan bilangan kompleks untuk x negatif dan bukan bilangan bulat y.
Jules
8
@ Jules pow (0, 0) tidak ditentukan.
emboss
110

Jawaban Hans Passant bagus, tetapi saya ingin menambahkan bahwa jika bbilangan bulat, maka a^bdapat dihitung dengan sangat efisien dengan dekomposisi biner. Berikut ini adalah versi modifikasi dari Henry Warren's 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;
    }    
}

Dia mencatat bahwa operasi ini optimal (apakah jumlah minimum operasi aritmatika atau logis) untuk semua b <15. Juga tidak ada solusi yang diketahui untuk masalah umum menemukan urutan faktor optimal untuk menghitung a^bb selain dari luas Cari. Ini masalah NP-Hard. Jadi pada dasarnya itu berarti bahwa dekomposisi biner sebaik yang didapatnya.

Michael Graczyk
sumber
11
Algoritma ini ( kuadrat dan gandakan ) juga berlaku jika amerupakan angka floating point.
CodesInChaos
14
Dalam praktiknya adalah mungkin untuk melakukan sedikit lebih baik daripada kuadrat-dan-gandakan. Misalnya menyiapkan tabel pencarian untuk eksponen kecil sehingga Anda dapat membuat persegi beberapa kali dan hanya kemudian mengalikan, atau membangun rantai penambahan persegi yang dioptimalkan untuk eksponen tetap. Masalah seperti ini merupakan bagian integral dari algoritma kriptografi yang penting, sehingga ada cukup banyak upaya untuk mengoptimalkannya. Kekerasan NP hanya tentang asimptotik kasus terburuk , kita sering dapat menghasilkan solusi yang optimal atau hampir tidak optimal untuk contoh masalah yang timbul dalam praktik.
CodesInChaos
Teks tidak menyebutkan amenjadi bilangan bulat, tetapi kode itu. Sebagai akibatnya, saya bertanya-tanya tentang keakuratan hasil perhitungan teks "sangat efisien".
Andrew Morton
69

Jika tersedia versi C daripow indikasi apa pun, itu tidak terlihat seperti apa yang Anda harapkan. Tidak akan banyak membantu bagi Anda untuk menemukan versi .NET, karena masalah yang Anda selesaikan (yaitu yang dengan bilangan bulat) adalah urutan besarnya yang lebih sederhana, dan dapat diselesaikan dalam beberapa baris kode C # dengan eksponensial oleh algoritma kuadrat .

dasblinkenlight
sumber
Terima kasih atas jawaban anda. Tautan pertama mengejutkan saya karena saya tidak mengharapkan implementasi teknis Pow () yang sangat besar. Meskipun jawaban Hans Passant menegaskan bahwa itu sama di dunia Net juga. Saya pikir saya bisa menyelesaikan masalah yang ada dengan memanfaatkan beberapa teknik yang tercantum dalam tautan algoritma kuadrat. Terima kasih lagi.
Pawan Mishra
2
Saya tidak percaya bahwa kode ini efisien. 30 variabel lokal seharusnya menabrak semua register. Saya hanya mengira itu adalah versi ARM, tetapi pada x86 30 variabel lokal dalam metode ini mengagumkan.
Alex Zhukovskiy