Standart C ++ kitaplıklarında neden `` int pow (int base, int exponent) '' yok?


116

Onu bulamayacakmışım gibi hissediyorum. C ++ powişlevinin "güç" işlevini floats ve doubles dışında hiçbir şey için uygulamamasının herhangi bir nedeni var mı ?

Uygulamanın önemsiz olduğunu biliyorum, sadece standart bir kitaplıkta olması gereken bir iş yaptığımı hissediyorum. Sağlam bir güç işlevi (yani taşmayı tutarlı ve açık bir şekilde ele alır) yazmak eğlenceli değildir.


4
Bu iyi bir soru ve cevapların pek mantıklı olduğunu düşünmüyorum. Negatif üsler çalışmıyor mu? İmzasız girişleri üs olarak alın. Girişlerin çoğu taşmasına neden oluyor mu? Aynı şey exp ve double pow için de geçerli, kimsenin şikayet ettiğini görmüyorum. Öyleyse neden bu işlev standart değil?
static_rtti

2
@static_rtti: "Aynı şey exp ve double pow için de doğrudur" tamamen yanlıştır. Cevabımda detaylandıracağım.
Stephen Canon

11
Standart C ++ kitaplığı double pow(int base, int exponent)C ++ 11'den beri (§26.8 [c.math] / 11 madde işareti 2)
Cubbi

'Uygulama önemsiz' ve 'yazmak eğlenceli değil' arasında karar vermeniz gerekiyor.
Marquis of Lorne

Yanıtlar:


66

Şu andan itibaren C++11, güç işlevleri grubuna (ve diğerleri) özel durumlar eklendi. C++11 [c.math] /11tüm float/double/long doubleaşırı yükleri listeledikten sonra (vurgularım ve başka kelimelerle ifade edilmiş):

Ayrıca, bir parametreye karşılık gelen herhangi bir bağımsız değişkenin türü veya bir tamsayı türüne sahip olması durumunda, parametrelere karşılık gelen tüm bağımsız değişkenlerin etkin bir şekilde dönüştürülmesini sağlamak için yeterli ek aşırı yükler olmalıdır .doubledoubledoubledouble

Yani, temelde, tamsayı parametreleri, işlemi gerçekleştirmek için iki katına yükseltilecektir.


Öncesinde C++11(sorunuz sorulduğu zamandı), tamsayı aşırı yüklemesi yoktu.

Ben ne yakından yaratıcıları ile ilişkiliydi yana Cne de C++(Gerçi onların yaratılış günlerinde değilim doğrusu eski), ne de standartlarını oluşturdu ANSI / ISO komitelerinin parçası, bu mutlaka benim açımdan görüşüm. Bunun bilinçli bir fikir olduğunu düşünmek isterim ama eşimin size söyleyeceği gibi (sık sık ve çok fazla teşvik gerekmeden), daha önce yanılmışım :-)

Varsayım, değeri ne olursa olsun, takip eder.

Ben şüpheli orijinal öncesi ANSI nedeni Ctamamen gereksiz olduğundan bu özelliği yoktu olduğunu. İlk olarak, tamsayı güçleri yapmanın mükemmel bir yolu vardı (çiftler ile ve sonra basitçe bir tam sayıya dönüştürmek, dönüştürmeden önce tamsayı taşması ve yetersizliği kontrol etmek).

İkincisi, hatırlamanız gereken başka bir şey de, asıl amacının Cbir sistem programlama dili olduğudur ve bu alanda kayan noktanın istenip istenmediği sorgulanabilir.

İlk kullanım durumlarından biri UNIX'i kodlamak olduğu için, kayan nokta neredeyse yararsız olurdu. C'nin dayandığı BCPL'nin de güçler için bir faydası yoktu (bellekten hiç kayan noktası yoktu).

Bir kenara, bir bütünleşik güç operatörü muhtemelen bir kütüphane çağrısı yerine bir ikili operatör olurdu. Kitaplıktan ziyade dilin bir parçası olan x = add (y, z)but with ile iki tamsayı eklemiyorsunuz .x = y + z

Üçüncüsü, integral gücün uygulanması görece önemsiz olduğundan, dil geliştiricilerinin zamanlarını daha yararlı şeyler sağlamak için daha iyi kullanacakları neredeyse kesindir (fırsat maliyetiyle ilgili aşağıdaki yorumlara bakınız).

Bu aynı zamanda orijinalle de ilgilidir C++. Orijinal uygulama etkili bir şekilde sadece Ckod üreten bir çevirmen olduğu için C,. Asıl amacı, sınıflarla-C-artı-biraz-ekstra-matematiksel şeyler değil, sınıflarla C idi.

Neden daha önce standartlara eklenmediğine gelince C++11, standartları belirleyen kurumların takip etmesi gereken belirli yönergeler olduğunu unutmamalısınız. Örneğin, ANSI Cözel olarak yeni bir dil yaratmak için değil , mevcut uygulamayı kodlamakla görevlendirildi . Aksi takdirde çıldırabilirlerdi ve bize Ada'yı verebilirlerdi :-)

Bu standardın daha sonraki yinelemeleri de belirli yönergelere sahiptir ve mantık belgelerinde bulunabilir (dilin kendisi için mantık değil, komitenin neden belirli kararlar aldığına ilişkin mantık).

Örneğin, C99gerekçe belgesi özellikle C89eklenebilecekleri sınırlayan iki yol gösterici ilkeyi ileri taşımaktadır :

  • Dili küçük ve basit tutun.
  • Bir işlemi yapmanın tek bir yolunu sağlayın.

Yönergeler ( belirli olanlar zorunlu değildir ) bireysel çalışma grupları için belirlenir ve bu nedenle C++komiteleri (ve diğer tüm ISO gruplarını) da sınırlar.

Buna ek olarak, standartları belirleyen kurumlar, aldıkları her kararın bir fırsat maliyeti (alınan bir karar için vazgeçmeniz gereken anlamına gelen ekonomik bir terim) olduğunun farkındadır . Örneğin, 10.000 dolarlık uber oyun makinesini satın almanın fırsat maliyeti, diğer yarınızla yaklaşık altı ay boyunca samimi ilişkiler (veya muhtemelen tüm ilişkiler).

Eric Gunnerson, Microsoft ürünlerine her şeyin neden her zaman eklenmediğine dair 100 puanlık açıklamasıyla bunu iyi açıklıyor - temelde bir özellik delikte 100 nokta başlatıyor, bu yüzden dikkate alınması için bile biraz değer katması gerekiyor.

Başka bir deyişle, bütünleşik bir güç operatörünü (dürüst olmak gerekirse, herhangi bir yarı-iyi kodlayıcı on dakikada kırbaçlayabilir) veya standarda eklenen çoklu iş parçacığı olmasını mı tercih edersiniz? Kendim için, ikincisine sahip olmayı ve UNIX ve Windows altındaki farklı uygulamalarla uğraşmak zorunda kalmamayı tercih ederim.

Standart kitaplığın binlerce ve binlerce koleksiyonunu da görmek isterim (karmalar, ağaç ağaçları, kırmızı-siyah ağaçlar, sözlük, rastgele haritalar vb.), Ancak mantıksal olarak şunu da belirtmek isterim:

Standart, uygulayıcı ve programcı arasındaki bir anlaşmadır.

Ve standart organlarındaki uygulayıcıların sayısı, programcıların (veya en azından fırsat maliyetini anlamayan programcıların) sayısından çok daha ağır basmaktadır. Tüm bu şeyler eklenirse, bir sonraki standart C++bundan C++215xüç yüz yıl sonra derleyici geliştiricileri tarafından tamamen uygulanacak ve muhtemelen uygulanacaktır.

Her neyse, bu konu hakkındaki (oldukça hacimli) düşüncelerim. Sadece oylar kaliteden çok niceliğe göre dağıtılırsa, yakında herkesi sudan çıkarırdım. Dinlediğin için teşekkürler :-)


2
FWIW, C ++ 'nın kısıtlama olarak "Bir işlemi yapmak için tek bir yol sağla" seçeneğini takip ettiğini düşünmüyorum. Doğru şekilde, çünkü örneğin to_stringve lambdaların ikisi de zaten yapabileceğiniz şeyler için kolaylık sağlar. Her ikisine de izin vermek için "bir işlemi yapmanın tek yolu" çok gevşek bir şekilde yorumlanabilir ve aynı zamanda "aha! Hayır!" tam olarak eşdeğer ama daha uzun soluklu alternatiften incelikle farklı bir işlem! ". Bu kesinlikle lambdalar için doğrudur.
Steve Jessop

@Steve, evet, bu benim açımdan kötü bir şekilde ifade edildi. Tüm komitelerin aynı kuralları takip etmesinden ziyade her komite için bir kılavuz olduğunu söylemek daha doğrudur. Clarifyl için Düzeltilmiş cevap
paxdiablo

2
Sadece bir nokta (birkaç noktadan): "herhangi bir kod maymunu on dakikada kırbaçlayabilir". Elbette ve eğer 100 kod maymun (güzel aşağılayıcı terim, BTW) bunu her yıl yaparsa (muhtemelen düşük bir tahmin), boşa harcanan 1000 dakikamız var. Çok verimli, değil mi?
Jürgen A.Erhard

1
@ Jürgen, aşağılayıcı olması amaçlanmadı (aslında etiketi belirli bir kişiye atfetmediğim için), bu sadece powçok fazla beceri gerektirmeyen bir göstergeydi . Kesinlikle ben standart bir şeyler sunmak olurdu olurdu çaba yinelenmiş olsaydı beceri bir çok gerektirir ve çok daha boşa dakika sonuçlanabilir.
paxdiablo

2
@ eharo2, mevcut metindeki "yarı düzgün kodlayıcı" yı "kod maymunu" ile değiştirin. Ben de aşağılayıcı olduğunu düşünmedim ama ihtiyatlı olmanın en iyisi olduğunu düşündüm ve dürüst olmak gerekirse şu anki üslup aynı fikirle karşılaşıyor.
paxdiablo

41

Herhangi bir sabit genişlikli integral tipi için, neredeyse tüm olası giriş çiftleri, yine de tipin dışına taşar. Olası girdilerinin büyük çoğunluğu için yararlı bir sonuç vermeyen bir işlevi standartlaştırmanın faydası nedir?

İşlevi kullanışlı hale getirmek için büyük bir tam sayı türüne sahip olmanız gerekir ve çoğu büyük tamsayı kitaplığı işlevi sağlar.


Düzenleme: Soruyla ilgili bir yorumda, static_rtti "Girişlerin çoğu taşmasına neden olur? Aynı şey exp ve double pow için de geçerlidir, kimsenin şikayet ettiğini görmüyorum." Bu yanlış.

Bir kenara bırakalım exp, çünkü konu bu değil (aslında benim durumumu daha da güçlendirecek olsa da) ve odaklanalım double pow(double x, double y). (X, y) çiftlerinin hangi kısmı için bu işlev yararlı bir şey yapar (yani, yalnızca taşma veya yetersizlik değil)?

Aslında powmantıklı olan girdi çiftlerinin sadece küçük bir kısmına odaklanacağım , çünkü bu benim noktamı kanıtlamak için yeterli olacaktır: eğer x pozitifse ve | y | <= 1, powbu durumda taşmaz veya altlanmaz. Bu, tüm kayan nokta çiftlerinin neredeyse dörtte birini kapsar (NaN olmayan kayan noktalı sayıların tam olarak yarısı pozitiftir ve NaN olmayan kayan noktalı sayıların yarısından azının büyüklüğü 1'den küçüktür). Açıkçası, faydalı sonuçlar üreten birçok başka girdi çifti var pow, ancak bunların tüm girdilerin en az dörtte biri olduğunu tespit ettik.

Şimdi sabit genişlikli (yani bignum olmayan) bir tamsayı kuvvet fonksiyonuna bakalım. Hangi kısım girdiler için basitçe taşmaz? Anlamlı giriş çiftlerinin sayısını en üst düzeye çıkarmak için, taban işaretli ve üs işaretsiz olmalıdır. Taban ve üssün her ikisinin de nbit genişliğinde olduğunu varsayalım . Girdilerin anlamlı olan kısmına kolayca sınır koyabiliriz:

  • Üs 0 veya 1 ise, herhangi bir baz anlamlıdır.
  • Üs 2 veya daha büyükse, 2 ^ (n / 2) 'den büyük taban anlamlı bir sonuç vermez.

Bu nedenle, 2 ^ (2n) giriş çiftlerinden 2 ^ (n + 1) + 2 ^ (3n / 2) 'den daha az anlamlı sonuçlar üretir. Muhtemelen en yaygın kullanım olan 32 bitlik tam sayılara bakarsak, bu, girdi çiftlerinin yüzde birinin 1000'de 1'i kadar olan bir şeyin basitçe taşmadığı anlamına gelir.


8
Her neyse, bunların hepsi tartışmalı. Bir işlevin bazı veya birçok girdi için geçerli olmaması onu daha az kullanışlı hale getirmez.
static_rtti

2
@static_rtti: pow(x,y)herhangi bir x için sıfırdan aşağı taşmaz if | y | <= 1. Alt taşmanın meydana geldiği çok dar bir girdi bandı (büyük x, y neredeyse -1) vardır, ancak sonuç bu aralıkta yine de anlamlıdır.
Stephen Canon

2
Daha fazla düşündükten sonra, aşağı akış konusunda hemfikirim. Yine de bunun soruyla alakalı olmadığını düşünüyorum.
static_rtti

7
@ybungalobill: Neden bunu bir sebep olarak seçtin? Kişisel olarak, çok sayıda sorun ve programcı için yararlılığı, çoğu programcının muhtemelen yazacağı naif uygulamadan daha hızlı, harware için optimize edilmiş sürümler yapma olasılığını tercih ederim. Kriteriniz tamamen keyfi görünüyor ve açıkçası oldukça anlamsız.
static_rtti

5
@StephenCanon: İşin güzel tarafı, argümanınız, tam sayının açıkça doğru ve en uygun şekilde uygulanmasının powsadece küçük bir arama tablosu olduğunu gösteriyor. :-)
R .. GitHub ICE YARDIMINI DURDUR

11

Zaten bir int'te tüm tamsayı güçlerini temsil etmenin bir yolu olmadığı için:

>>> print 2**-4
0.0625

3
Sonlu boyutlu bir sayısal tür için, taşma nedeniyle bu türdeki tüm güçleri temsil etmenin bir yolu yoktur. Ama negatif güçlerle ilgili fikriniz daha geçerli.
Chris Lutz

1
Negatif üsleri, üs olarak işaretsiz bir int alarak veya girdi olarak negatif bir üs sağlandığında ve bir int beklenen çıktı olduğunda sıfır döndürerek standart bir gerçeklemenin üstesinden gelebileceği bir şey olarak görüyorum.
Dan O

3
veya ayrı olması int pow(int base, unsigned int exponent)vefloat pow(int base, int exponent)
Ponkadoodle

4
Negatif bir tamsayı geçirmek için bunu tanımsız bir davranış olarak ilan edebilirler.
Johannes Schaub - litb

2
Tüm modern uygulamalarda, bunun ötesinde herhangi bir şey int pow(int base, unsigned char exponent)zaten bir şekilde yararsızdır. Ya taban 0 ya da 1'dir ve üs önemli değildir, -1'dir, bu durumda yalnızca son üs biti önemlidir veya base >1 || base< -1bu durumda exponent<256taşma cezası söz konusudur .
MSalters

9

Bu aslında ilginç bir soru. Tartışmada bulamadığım bir argüman, argümanlar için bariz dönüş değerlerinin basit eksikliğidir. Varsayımsal int pow_int(int, int)işlevin başarısız olabileceği yolları sayalım .

  1. taşma
  2. Sonuç tanımsız pow_int(0,0)
  3. Sonuç temsil edilemez pow_int(2,-1)

Fonksiyonun en az 2 arıza modu vardır. Tamsayılar bu değerleri temsil edemez, bu durumlarda işlevin davranışının standart tarafından tanımlanması gerekir - ve programcıların işlevin bu durumları tam olarak nasıl işlediğinin farkında olması gerekir.

Genel olarak işlevi devre dışı bırakmak, mantıklı tek seçenek gibi görünüyor. Programcı, bunun yerine mevcut tüm hata raporlama ile kayan noktalı sürümü kullanabilir.


Ancak ilk iki durum, powara geçişler için de geçerli olmaz mıydı? İki büyük şamandıra alın, birini diğerinin gücüne yükseltin ve bir Taşma var. Ve pow(0.0, 0.0)2. noktanızla aynı soruna neden olur. 3. noktanız, tamsayılar ile kayan sayılar için bir güç işlevi uygulamak arasındaki tek gerçek farktır.
numbermaniac

7

Kısa cevap:

Doğal bir sayının pow(x, n)nerede nolduğuna dair bir uzmanlık genellikle zaman performansı için yararlıdır . Ancak standart kitaplığın jenerik , bu amaç için pow()hala oldukça iyi çalışıyor ( şaşırtıcı bir şekilde! ) Ve standart C kitaplığına mümkün olduğunca az yer verilmesi kesinlikle çok önemlidir, böylece taşınabilir ve mümkün olduğunca kolay uygulanabilir hale getirilebilir. Öte yandan, bu C ++ standart kitaplığında veya STL'de olmasını engellemiyor, ki eminim ki hiç kimse bir tür gömülü platformda kullanmayı planlamıyor.

Şimdi, uzun cevap için.

pow(x, n)birçok durumda ndoğal bir sayı konusunda uzmanlaşarak çok daha hızlı yapılabilir . Yazdığım hemen hemen her program için bu işlevi kendi uygulamamı kullanmak zorunda kaldım (ama C'de çok sayıda matematiksel program yazıyorum). Özel operasyon O(log(n))zamanında yapılabilir , ancak nküçük olduğunda daha basit bir doğrusal versiyon daha hızlı olabilir. İşte her ikisinin de uygulamaları:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(Bıraktım xve dönüş değerini ikiye katladım, çünkü sonuç ikiye katlanacak pow(double x, unsigned n)kadar sık ​​sık pow(double, double)olacak.)

(Evet, pownözyinelemelidir, ancak maksimum yığın boyutu kabaca eşit olacağından log_2(n)ve nbir tam sayı olduğundan yığını kırmak kesinlikle imkansızdır . n64 bitlik bir tamsayı ise, bu size yaklaşık 64'lük bir maksimum yığın boyutu verir. Hiçbir donanımda bu kadar uç nokta yoktur. bellek sınırlamaları, donanım yığınlarına sahip bazı tehlikeli PIC'ler hariç, yalnızca 3 ila 8 işlev çağrıları derinleşir.)

Performansa gelince, bir bahçe çeşidinin neler pow(double, double)yapabileceğine şaşıracaksınız . 5 yaşındaki IBM Thinkpad'imde yüz milyon yinelemeyi xyineleme sayısına neşit ve 10'a eşit olarak test ettim . Bu senaryoda pown_lkazandım. glibc pow()12.0 kullanıcı saniyesi pownaldı, 7.4 kullanıcı saniyesi pown_laldı ve sadece 6.5 kullanıcı saniyesi aldı. Yani bu çok şaşırtıcı değil. Aşağı yukarı bunu bekliyorduk.

Sonra, xsabit kalmasına izin verdim (2.5'e ayarladım) ve n0'dan 19'a yüz milyon kez döngü yaptım . Bu sefer, oldukça beklenmedik bir şekilde, glibc powkazandı ve heyelanla! Yalnızca 2.0 kullanıcı saniye sürdü. Benim pown9.6 saniye sürdü ve pown_l12.2 saniye sürdü. Burada ne oldu? Öğrenmek için başka bir test yaptım.

Yukarıdaki ile aynı şeyi sadece xbir milyona eşit olarak yaptım . Bu sefer pown9.6'larda kazandı. pown_l12.2 saniye ve glibc pow 16.3 saniye aldı. Şimdi, temiz! glibc düşük powolduğunda üçünden daha iyi , yüksek xolduğunda xise en kötü performans gösterir . Ne zaman xyüksek, pown_lzaman en üstün performans ndüşük ve pownen üstün performans zaman xyüksektir.

İşte her biri doğru koşullar altında diğerlerinden daha iyi performans gösterebilen üç farklı algoritma. Yani, sonuçta, büyük olasılıkla kullanılacak kullanımıyla ilgili nasıl sen planlamaya bağlıdır pow, ama doğru sürümünü kullanıyor olan değer o, ve sürümlerin hepsi sahip güzel. Aslında, aşağıdaki gibi bir işlevle algoritma seçimini otomatik hale bile getirebilirsiniz:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

Sürece x_expectedve n_expectedvardır muhtemelen diğer bazı uyarılar ile birlikte derleme zamanında karar sabitler, tuz otomatik olarak tüm kaldıracaktır bir optimizasyon derleyicisi değerinde pown_autoişlev çağrısını ve üç algoritmaları uygun seçimi ile değiştirin. (Şimdi, bunu gerçekten kullanmayı deneyecekseniz , muhtemelen onunla biraz oynamak zorunda kalacaksınız, çünkü tam olarak yukarıda yazdıklarımı derlemeyi denemedim .;))

Öte yandan, glibc pow çalışıyor ve glibc zaten yeterince büyük. C standardı, çeşitli gömülü aygıtlar da dahil olmak üzere taşınabilir olmalıdır (aslında her yerdeki yerleşik geliştiriciler glibc'nin kendileri için zaten çok büyük olduğu konusunda hemfikirdir) ve her basit matematik işlevi için her şeyi içermesi gerekiyorsa taşınabilir olamaz. alternatif algoritma olabilir kullanım olun. Bu yüzden C standardında değil.

dipnot: Zaman performans testinde, işlevlerime göreceli olarak cömert optimizasyon bayrakları ( -s -O2) verdim, bu işaretler muhtemelen sistemimde (archlinux) glibc'yi derlemek için kullanılanla karşılaştırılabilir, hatta bundan daha kötü değil, dolayısıyla sonuçlar muhtemelen fuar. Daha titiz bir test için, glibc'yi kendim derlemem gerekir ve gerçekten bunu yapmak istemiyorum. Gentoo kullanıyordum, bu yüzden görev otomatikleştirilse bile ne kadar sürdüğünü hatırlıyorum . Sonuçlar benim için yeterince kesin (veya sonuçsuz). Elbette bunu kendiniz yapabilirsiniz.

Bonus yuvarlak: Bir uzmanlaşma pow(x, n)tüm tamsayılar için enstrümantal tam bir tamsayı çıktı meydana geliyor ki, gerekirse. P ^ N elemanlı N boyutlu bir dizi için bellek ayırmayı düşünün. Bir p ^ N iptali, muhtemelen rastgele meydana gelen bir segfault ile sonuçlanacaktır.


Sanırım özyinelemeden kurtulursanız, yığın tahsisi için gereken zamandan tasarruf edersiniz. Ve evet, gücün her şeyi yavaşlattığı ve kendi gücümüzü uygulamamız gereken bir durum yaşadık.
Sambatyon

"Kimsenin aşırı hafıza sınırlamaları yoktur" yanlıştır. PIC genellikle maksimum 3 (örnek PIC10F200) ila 8 (örnek 16F722A) çağrı için sınırlı bir çağrı yığınına sahiptir (PIC, işlev çağrıları için bir donanım yığını kullanır).
12431234123412341234123

oh, adamım bu acımasız lol. Tamam, bu yüzden bu PIC'lerde çalışmayacak.
enigmaticPhysicist

Bir tamsayı tabanı ve güç için, sorunun sorulduğu gibi, derleyiciler (gcc ve clang) yinelemeli (özyinelemeli) bir uygulamadan kolayca dalsız bir döngü üretecektir. Bu, her bir parçasından dalların yanlış tahminlerini önler n. godbolt.org/z/L9Kb98 . gcc ve clang özyinelemeli tanımınızı basit bir döngüde optimize etmekte başarısız olurlar ve aslında n. (Çünkü pown_iter(double,unsigned)hala dallanıyorlar, ancak dalsız bir SSE2 veya SSE4.1 uygulaması x86 asm'de veya C intrinsics ile mümkün olmalı. Ama bu bile özyinelemeden daha iyi)
Peter Cordes

Kahretsin, şimdi emin olmak için döngü tabanlı bir sürümle kıyaslamaları baştan yapmam gerekiyor. Ben bu konuda düşüneceğim.
enigmaticPhysicist


3

Dünya ve programlama dilleri sürekli olarak gelişmektedir. C ondalık TR dördüncü bölümü ¹ için biraz daha fazla işlevleri ekler <math.h>. Bu işlevlerin iki ailesi bu soru için ilgi çekici olabilir:

  • pownİşlememesidir bir kayan noktalı numarası ve bir sürer intmax_tÜs.
  • powrFonksiyonlar, iki yüzer noktaları numaralarını (alır xve y) ve bilgi işlem xgücüne yformülü ile exp(y*log(x)).

Görünüşe göre standart adamlar sonunda bu özellikleri standart kitaplığa entegre edilecek kadar yararlı buldular. Ancak mantıklı olan, bu işlevlerin ikili ve ondalık kayan nokta sayıları için ISO / IEC / IEEE 60559: 2011 standardı tarafından tavsiye edilmesidir . C89 zamanında hangi "standardın" izlendiğini kesin olarak söyleyemem, ancak gelecekteki gelişmeler <math.h>muhtemelen ISO / IEC / IEEE 60559 standardının gelecekteki gelişmelerinden büyük ölçüde etkilenecektir .

Ondalık TR'nin dördüncü kısmının C2x'e (sonraki büyük C revizyonu) dahil edilmeyeceğini ve muhtemelen daha sonra isteğe bağlı bir özellik olarak dahil edileceğini unutmayın. Gelecekteki bir C ++ revizyonuna TR'nin bu bölümünü dahil etmek için bildiğim herhangi bir niyet yoktu.


¹ Devam etmekte olan bazı belgeleri burada bulabilirsiniz .


Kullanarak her hangi bir makul uygulamaları vardır powndaha bir üs daha sonra ile LONG_MAXhiç kullanarak arasında bir değer farklı verim gerekir LONG_MAX, ya da burada bir değer daha az LONG_MINbir değer farklı verim olmalıdır LONG_MIN? intmax_tBir üs için kullanmaktan ne gibi yararlar elde edildiğini merak ediyorum.
supercat

@supercat Fikrim yok, üzgünüm.
Morwenn

Standarda bakıldığında, aynı zamanda, eğer tanımlanırsa, "pown" ın doğru şekilde yuvarlatılmış bir versiyonu olabilecek isteğe bağlı bir "crpown" fonksiyonunu da tanımladığını belirtmekte fayda var; Standart aksi takdirde gerekli doğruluk derecesini belirtmez. Hızlı ve orta derecede hassas bir "pown" uygulamak kolaydır, ancak her durumda doğru yuvarlamayı sağlamak çok daha pahalı olacaktır.
supercat

2

Belki de işlemcinin ALU'su tamsayılar için böyle bir işlev uygulamadığından, ancak böyle bir FPU talimatı vardır (Stephen'ın da işaret ettiği gibi, bu aslında bir çifttir). Dolayısıyla, tamsayı aritmetiği kullanarak uygulamaktan, ikiye katlamak, ikiye katlayarak güç çağırmak, ardından taşma için test etmek ve geri çevirmek aslında daha hızlıydı.

(Bir kere, logaritmalar güçleri çarpmaya indirger, ancak tamsayıların logaritmaları çoğu girdi için çok fazla doğruluk kaybeder)

Stephen, modern işlemcilerde bunun artık doğru olmadığı konusunda haklı, ancak matematik işlevleri seçildiğinde (C ++ sadece C işlevlerini kullandı) C standardı şimdi ne, 20 yaşında?


5
İçin FPU komutu olan herhangi bir güncel mimari bilmiyorum pow. x86, bir işlevin ilk parçası olarak kullanılabilen bir y log2 xtalimata ( fyl2x) sahiptir pow, ancak powbu şekilde yazılan bir işlevin mevcut donanımda çalıştırılması yüzlerce döngü gerektirir; iyi yazılmış bir tamsayı üs alma rutini birkaç kat daha hızlıdır.
Stephen Canon

"Yüzlerce" ifadesinin doğru olduğunu bilmiyorum, çoğu modern CPU'larda fyl2x ve ardından f2xm1 için yaklaşık 150 döngü gibi görünüyor ve bu da diğer talimatlarla bağlantı kuruyor. Ancak, IMUL kayan noktalı talimatlardan çok daha fazla hızlandırıldığından, iyi ayarlanmış bir tamsayı uygulamasının (bugünlerde) çok daha hızlı olması gerektiği konusunda haklısınız. C standardı yazıldığı zaman, IMUL oldukça pahalıydı ve onu bir döngüde kullanmak muhtemelen FPU kullanmaktan daha uzun sürdü.
Ben Voigt

2
Düzeltmenin ışığında oyumu değiştirdim; yine de, (a) C standardının 1999'da büyük bir revizyondan geçtiğini (matematik kütüphanesinin büyük bir genişlemesi dahil) ve (b) C standardının herhangi bir belirli işlemci mimarisine yazılmadığını - varlığı veya x86'da FPU talimatlarının yokluğunun esasen C komitesinin standartlaştırmayı seçtiği işlevsellikle hiçbir ilgisi yoktur.
Stephen Canon

Herhangi bir mimariye bağlı değil, doğru, ancak bir arama tablosu enterpolasyonunun göreceli maliyeti (genellikle kayan nokta uygulaması için kullanılır), tamsayı çarpımına kıyasla, tahmin edeceğim tüm mimariler için hemen hemen eşit şekilde değişti.
Ben Voigt

1

İşte tamsayılar da dahil olmak üzere tüm sayısal türler için çalışan gerçekten basit bir O (log (n)) pow () uygulaması :

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

EnigmaticPhysicist'in O (log (n)) uygulamasından daha iyidir çünkü özyineleme kullanmaz.

Ayrıca, doğrusal uygulamasından hemen hemen her zaman daha hızlıdır (p> ~ 3 olduğu sürece) çünkü:

  • fazladan hafıza gerektirmez
  • döngü başına yalnızca ~ 1.5 kat daha fazla işlem yapar
  • döngü başına yalnızca ~ 1,25 kat daha fazla bellek güncellemesi yapar

-2

Aslında öyle.

C ++ 11'den beri pow(int, int)--- ve hatta daha genel durumlar için şablon uygulaması vardır , bkz. (7) http://en.cppreference.com/w/cpp/numeric/math/pow


DÜZENLEME: Püristler, aslında kullanılan "yükseltilmiş" tipleme olduğundan, bunun doğru olmadığını iddia edebilirler. Öyle ya da böyle, biri parametrelerde doğru intsonuç ya da hata alır int.


2
bu yanlış. (7) aşırı yüklemeye pow ( Arithmetic1 base, Arithmetic2 exp )dönüştürülecek doubleya long doubleda açıklamayı okuduysanız: "7) Bir dizi aşırı yüklenme veya 1-3'te kapsanmayan aritmetik tipteki tüm bağımsız değişken kombinasyonları için bir işlev şablonu. integral tipine sahiptir, double'a çevrilir. Herhangi bir argüman long double ise, o zaman Promoted dönüş tipi de long double, aksi takdirde dönüş tipi her zaman double olur. "
phuclv

burada yanlış olan ne? Sadece bugünlerde (C ++ 11'den beri) standart kitaplıkta bir şablonun ( , ) olduğunu
söyledim

5
Hayır değil. Tapınaklar bu türleri ikiye veya uzun ikiye katlamaya teşvik eder. Yani alttaki çiftler üzerinde çalışıyor.
Trismegistos

1
@Trismegistos Hala int parametrelerine izin verir. Eğer bu şablon orada olmasaydı, int parametrelerini geçirmek, int içindeki bitleri bir float olarak yorumlamasına ve keyfi beklenmedik sonuçlara neden olur. Aynı şey karışık girdi değerlerinde de olur. eg pow(1.5f, 3)= 1072693280but pow(1.5f, float(3))=3.375
Mark Jeronimus

2
OP istedi int pow(int, int), ancak C ++ 11 yalnızca sağlar double pow(int, int). @ Phuclv'nin açıklamasına bakın.
xuhdev

-4

Çok basit bir sebep:

5^-2 = 1/25

STL kitaplığındaki her şey akla gelebilecek en doğru ve sağlam öğelere dayanmaktadır. Elbette, int sıfıra (1 / 25'ten) dönecektir, ancak bu yanlış bir cevap olacaktır.

Katılıyorum, bazı durumlarda tuhaf.


3
İmzasız bir ikinci argüman talep etmek tabii ki gereklidir. Yalnızca negatif olmayan tamsayı güçleri gerektiren birçok uygulama vardır.
enigmaticPhysicist
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.