Math.Pow () .NET Framework'te nasıl uygulanır?


433

Bir b hesaplamak için etkili bir yaklaşım arıyordum (söyle a = 2ve b = 50). İşleri başlatmak için, Math.Pow()işlevin uygulanmasına bir göz atmaya karar verdim . Ama .NET Reflector'da bulduğum tek şey şuydu:

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

Math.Pow()İşlev dediğimde içeride neler olduğunu görebildiğim kaynaklardan bazıları nelerdir ?


15
Tıpkı bir FYI gibi, bütünü InternalCallbir externdeğiştiriciyle karıştırıyorsanız ( çelişkili göründükleri gibi ), lütfen bu aynı şey hakkında gönderdiğim soruya (ve sonuçta ortaya çıkan cevaplara) bakın .
CraigTP

6
Bir 2^xişlem xiçin tamsayı ise sonuç bir kaydırma işlemidir. Belki de sonucu bir mantis 2ve üssü kullanarak inşa edebilirsiniz x.
ja72

@SurajJain yorumunuz aslında ayrıca göndermeniz gereken bir soru.
ja72

@SurajJain Sana katılıyorum. Ben moderatör değilim, bu yüzden burada fazla bir şey yapamam. Belki de downvote soru meta.stackoverflow.com adresinde sorulabilir
ja72

Yanıtlar:


855

MethodImplOptions.InternalCall

Bu, yöntemin aslında C ++ ile yazılmış CLR'de uygulandığı anlamına gelir. Tam zamanında derleyici, dahili olarak uygulanan yöntemlerle bir tabloya danışır ve C ++ işlevine yapılan çağrıyı doğrudan derler.

Koda bir göz atmak CLR için kaynak kodunu gerektirir. Bunu SSCLI20 dağıtımından alabilirsiniz . .NET 2.0 zaman dilimi etrafında yazılmıştır, düşük seviyeli uygulamaları buldum Math.Pow(), CLR'nin sonraki sürümleri için hala büyük ölçüde doğru olmak istiyorum .

Arama tablosu clr / src / vm / ecall.cpp dizininde bulunur. Alakalı bölüm şu Math.Pow()şekildedir:

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" araması sizi clr / src / classlibnative / float / comfloat.cpp adresine götürür. Sana kodu yedekleyeceğim, sadece kendine bir bak. Temel olarak köşe durumlarını kontrol eder, ardından CRT'nin sürümünü çağırır pow().

İlginç olan diğer tek uygulama detayı tablodaki FCIntrinsic makrosudur. Bu, titreşimin işlevi içsel olarak uygulayabileceğine dair bir ipucu. Başka bir deyişle, işlev çağrısını bir kayan noktalı makine kodu komutuyla değiştirin. Bu durum böyle değil Pow(), bunun için FPU talimatı yok. Ama kesinlikle diğer basit operasyonlar için. Dikkat çeken, bu C # 'daki kayan nokta matematiğini C ++' daki aynı koddan önemli ölçüde daha hızlı hale getirebilir, bu nedenle bu cevabı kontrol edin .

Bu arada, Visual Studio vc / crt / src dizininin tam sürümüne sahipseniz CRT için kaynak kodu da kullanılabilir. pow()Yine de duvara çarpacaksınız , Microsoft bu kodu Intel'den satın aldı. Intel mühendislerinden daha iyi bir iş yapmak pek olası değildir. Lise kitabımın kimliği denediğimde iki kat daha hızlı olmasına rağmen:

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

Ancak gerçek bir yedek değildir, çünkü 3 kayan nokta işleminden hata biriktirir ve Pow () 'in sahip olduğu tuhaf etki alanı sorunlarıyla ilgilenmez. 0 ^ 0 ve -Infinity gibi herhangi bir güce yükseltildi.


437
Harika bir cevap, StackOverflow'un 'Bunu neden bilmek istersiniz?' bu çok sık oluyor.
Tom W

16
@Mavi - Bilmiyorum, Intel mühendisleriyle dalga geçmekten kısa. Lise kitabımın bir şeyi negatif bir integralin gücüne yükseltmekte bir sorunu var. Pow (x, -2) mükemmel şekilde hesaplanabilir, Pow (x, -2.1) tanımlanmamıştır. Etki alanı sorunları başa çıkmak için bir sürtük.
Hans Passant

12
@ BlueRaja-DannyPflughoeft: Kayan nokta işlemlerinin doğru yuvarlanmış değere olabildiğince yakın olmasını sağlamak için çok çaba harcanıyor. powaşkın bir işlev olan, doğru bir şekilde uygulanması herkesin bildiği gibi zordur (bkz. Tablo Yapıcının İkilemi ). Entegre bir güçle çok daha kolay.
porges

9
@Hans Passant: Pow (x, -2.1) neden tanımsız olsun? Matematiksel olarak pow tüm x ve y için her yerde tanımlanır. Negatif x ve tamsayı olmayan y için karmaşık sayılar elde etme eğilimindesiniz.
Jules

8
@Jules pow (0, 0) tanımlanmamıştır.
kabartma

110

Hans Passant'ın cevabı harika, ancak bbir tamsayı ise , a^bikili ayrışma ile çok verimli bir şekilde hesaplanabileceğini eklemek istiyorum . İşte Henry Warren'ın Hacker Delight'ının değiştirilmiş bir versiyonu :

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;
    }    
}

O Bu işlem en uygun olduğunu belirtmektedir tüm b <bilgi işlem için faktörlerin optimal sekans bulma genel bir sorun 15. Ayrıca hiçbir bilinmekte olup çözeltisi (aritmetik veya mantıksal işlemler minimum sayıda yapar) a^bgeniş dışında herhangi bir b arama. NP-Zor bir problem. Yani temelde bu ikili ayrışmanın olabildiğince iyi olduğu anlamına gelir.


11
Bu algoritma ( kare ve çarpma ) abir kayan noktalı sayı ise de geçerlidir .
CodesInChaos

14
Pratikte, yerel kare ve çarpma işleminden biraz daha iyi yapmak mümkündür. Örneğin, küçük üsler için arama tabloları hazırlamak, böylece birkaç kez kare oluşturabilir ve ancak daha sonra çoğaltabilir veya sabit üsler için optimize edilmiş kare ekleme zincirleri oluşturabilirsiniz. Bu tür bir sorun, önemli şifreleme algoritmalarının ayrılmaz bir parçasıdır, bu nedenle onu optimize etmek için biraz çalışma yapıldı. NP sertliği sadece en kötü durumdaki asimptotiklerle ilgilidir , pratikte ortaya çıkan problem örnekleri için çoğu zaman en uygun ya da en uygun olan çözümleri üretebiliriz.
CodesInChaos

Metin abir tamsayıdan bahsetmez , ancak koddan bahseder . Bunun bir sonucu olarak, metnin "çok verimli" hesaplamasının sonucunun doğruluğunu merak ediyorum .
Andrew Morton

69

Eğer serbestçe mevcut C versiyonupow herhangi bir gösterge ise, beklediğiniz bir şey gibi görünmüyor. .NET sürümünü bulmanız size çok yardımcı olmaz, çünkü çözdüğünüz sorun (yani tamsayılarla olan) büyüklüklerin siparişleri daha basittir ve üs ile birlikte birkaç satır C # kodunda çözülebilir kareleme algoritması ile .


Cevabınız için teşekkürler. İlk bağlantı beni şaşırttı çünkü Pow () işlevinin bu kadar büyük teknik uygulamasını beklemiyordum. Her ne kadar Hans Passant cevabı .net dünyasında da aynı olduğunu doğrular. Kareleme algoritması bağlantısında listelenen tekniklerden bazılarını kullanarak sorunu çözebileceğimi düşünüyorum. Tekrar teşekkürler.
Pawan Mishra

2
Bu kodun etkili olduğuna inanmıyorum. 30 yerel değişken sadece tüm kayıtları çarpmalıdır. Ben sadece onun ARM sürümü olduğunu düşünüyorum, ama yöntem x86 30 yerel değişkenler harika.
Alex Zhukovskiy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.