Saya merasa seperti saya pasti tidak dapat menemukannya. Apakah ada alasan bahwa fungsi C ++ pow
tidak mengimplementasikan fungsi "daya" untuk apa pun kecuali float
s dan double
s?
Saya tahu implementasinya sepele, saya hanya merasa seperti melakukan pekerjaan yang seharusnya ada di perpustakaan standar. Fungsi daya yang kuat (yaitu menangani luapan dengan cara yang konsisten dan eksplisit) tidak menyenangkan untuk ditulis.
double pow(int base, int exponent)
sejak C ++ 11 (§26.8 [c.math] / 11 butir poin 2)Jawaban:
Pada
C++11
, kasus khusus ditambahkan ke rangkaian fungsi daya (dan lainnya).C++11 [c.math] /11
menyatakan, setelah mencantumkan semuafloat/double/long double
kelebihan (penekanan saya, dan parafrase):Jadi, pada dasarnya, parameter integer akan ditingkatkan menjadi dua kali lipat untuk melakukan operasi.
Sebelumnya
C++11
(saat pertanyaan Anda diajukan), tidak ada kelebihan bilangan bulat.Karena saya tidak terkait erat dengan pencipta
C
atauC++
pada hari-hari penciptaan mereka (meskipun saya saya agak lama), juga bagian dari komite ANSI / ISO yang menciptakan standar, ini tentu pendapat di bagian saya. Saya ingin berpikir itu adalah opini yang diinformasikan tetapi, karena istri saya akan memberi tahu Anda (sering dan tanpa banyak dorongan yang dibutuhkan), saya pernah salah sebelumnya :-)Anggapan, untuk apa nilainya, berikut.
Saya menduga bahwa alasan pra-ANSI asli
C
tidak memiliki fitur ini adalah karena itu sama sekali tidak diperlukan. Pertama, sudah ada cara yang sangat baik untuk melakukan pangkat integer (dengan dua kali lipat dan kemudian hanya mengubahnya kembali ke integer, memeriksa overflow dan underflow integer sebelum mengonversi).Kedua, hal lain yang harus Anda ingat adalah bahwa maksud asli dari
C
adalah sebagai bahasa pemrograman sistem , dan itu dipertanyakan apakah floating point diinginkan di arena itu sama sekali.Karena salah satu kasus penggunaan awalnya adalah untuk membuat kode UNIX, titik mengambang akan menjadi tidak berguna. BCPL, yang menjadi dasar C, juga tidak menggunakan kekuatan (tidak memiliki floating point sama sekali, dari memori).
Ketiga, karena penerapan kekuatan integral relatif sepele, hampir dapat dipastikan bahwa pengembang bahasa akan lebih baik menggunakan waktu mereka untuk menyediakan hal-hal yang lebih bermanfaat (lihat komentar di bawah tentang biaya peluang).
Itu juga relevan dengan aslinya
C++
. Karena implementasi asli secara efektif hanya penerjemah yang menghasilkanC
kode, itu membawa banyak atributC
. Maksud aslinya adalah C-dengan-kelas, bukan C-dengan-kelas-plus-sedikit-tambahan-matematika-barang.Mengenai mengapa tidak pernah ditambahkan ke standar sebelumnya
C++11
, Anda harus ingat bahwa badan pembuat standar memiliki pedoman khusus untuk diikuti. Misalnya, ANSIC
secara khusus ditugaskan untuk menyusun praktik yang ada, bukan untuk membuat bahasa baru. Kalau tidak, mereka bisa jadi gila dan memberi kami Ada :-)Iterasi selanjutnya dari standar itu juga memiliki pedoman khusus dan dapat ditemukan dalam dokumen rasional (alasan mengapa panitia membuat keputusan tertentu, bukan alasan bahasa itu sendiri).
Misalnya,
C99
dokumen dasar pemikiran secara khusus mengedepankan duaC89
prinsip panduan yang membatasi apa yang dapat ditambahkan:Panduan (tidak harus yang spesifik ) ditetapkan untuk masing-masing kelompok kerja dan karenanya membatasi
C++
komite (dan semua kelompok ISO lainnya) juga.Selain itu, badan-badan pembuat standar menyadari bahwa ada biaya peluang (istilah ekonomi yang berarti apa yang harus Anda tinggalkan untuk membuat keputusan) untuk setiap keputusan yang mereka buat. Misalnya, biaya peluang untuk membeli mesin game uber seharga $ 10.000 itu adalah hubungan baik (atau mungkin semua hubungan) dengan separuh lainnya selama sekitar enam bulan.
Eric Gunnerson menjelaskan hal ini dengan baik dengan penjelasan -100 poinnya tentang mengapa hal-hal tidak selalu ditambahkan ke produk Microsoft - pada dasarnya fitur memulai 100 poin di lubang sehingga harus menambahkan sedikit nilai untuk dipertimbangkan.
Dengan kata lain, apakah Anda lebih suka memiliki operator daya integral (yang, sejujurnya, pembuat kode setengah layak mana pun dapat menyiapkan dalam sepuluh menit) atau multi-threading ditambahkan ke standar? Bagi saya sendiri, saya lebih suka memiliki yang terakhir dan tidak perlu repot dengan implementasi yang berbeda di bawah UNIX dan Windows.
Saya juga ingin melihat ribuan dan ribuan koleksi perpustakaan standar (hashes, btree, pohon merah-hitam, kamus, peta sembarang, dan sebagainya), tetapi, seperti yang dinyatakan oleh alasan:
Dan jumlah pelaksana di badan standar jauh lebih banyak daripada jumlah pemrogram (atau setidaknya pemrogram yang tidak memahami biaya peluang). Jika semua itu ditambahkan, standar berikutnya
C++
akanC++215x
dan mungkin akan diimplementasikan sepenuhnya oleh pengembang kompilator tiga ratus tahun setelah itu.Bagaimanapun, itu adalah pemikiran saya (yang agak banyak) tentang masalah ini. Andai saja suara diberikan berdasarkan kuantitas daripada kualitas, saya akan segera membuat semua orang tersingkir. Terima kasih untuk mendengarkan :-)
sumber
to_string
dan lambda adalah kemudahan untuk hal-hal yang sudah dapat Anda lakukan. Saya kira orang dapat menafsirkan "hanya satu cara untuk melakukan suatu operasi" dengan sangat longgar untuk memungkinkan keduanya, dan pada saat yang sama memungkinkan hampir semua duplikasi fungsi yang dapat dibayangkan, dengan mengatakan "aha! Tidak! Karena kemudahan membuat itu operasi yang sedikit berbeda dari alternatif yang persis sama tetapi lebih bertele-tele! ". Yang benar tentang lambda.pow
tidak benar-benar membutuhkan banyak keterampilan. Tentu saja saya lebih suka standar menyediakan sesuatu yang akan membutuhkan banyak keterampilan, dan menghasilkan lebih banyak menit terbuang percuma jika usaha itu harus diduplikasi.Untuk tipe integral dengan lebar tetap, hampir semua pasangan input yang memungkinkan melimpah tipe tersebut. Apa gunanya standarisasi fungsi yang tidak memberikan hasil yang berguna untuk sebagian besar kemungkinan inputnya?
Anda perlu memiliki tipe integer yang besar untuk membuat fungsi tersebut berguna, dan sebagian besar library integer menyediakan fungsinya.
Edit: Dalam komentar pada pertanyaan, static_rtti menulis "Sebagian besar input menyebabkannya meluap? Hal yang sama berlaku untuk exp dan double pow, saya tidak melihat ada yang mengeluh." Ini salah
Mari kita kesampingkan
exp
, karena itu tidak penting (meskipun itu sebenarnya akan membuat kasus saya lebih kuat), dan fokuslahdouble pow(double x, double y)
. Untuk bagian mana dari pasangan (x, y) fungsi ini melakukan sesuatu yang berguna (yaitu, tidak hanya meluap atau meluap)?Saya sebenarnya akan fokus hanya pada sebagian kecil dari pasangan input yang
pow
masuk akal, karena itu akan cukup untuk membuktikan poin saya: jika x positif dan | y | <= 1, makapow
tidak overflow atau underflow. Ini terdiri dari hampir seperempat dari semua pasangan floating-point (tepatnya setengah dari bilangan floating-point non-NaN adalah positif, dan hanya kurang dari separuh bilangan floating-point non-NaN yang besarnya kurang dari 1). Jelas, ada banyak pasangan masukan lain yang memberikanpow
hasil berguna, tetapi kami telah memastikan bahwa setidaknya seperempat dari semua masukan.Sekarang mari kita lihat fungsi bilangan bulat dengan lebar tetap (yaitu non-bignum). Untuk bagian apa masukan tidak meluap begitu saja? Untuk memaksimalkan jumlah pasangan input yang berarti, basis harus ditandatangani dan eksponen tidak ditandatangani. Misalkan basis dan eksponen keduanya memiliki
n
lebar bit. Kita bisa dengan mudah mendapatkan batasan pada porsi input yang berarti:Jadi, dari 2 ^ (2n) pasangan masukan, kurang dari 2 ^ (n + 1) + 2 ^ (3n / 2) menghasilkan hasil yang berarti. Jika kita melihat kemungkinan penggunaan paling umum, bilangan bulat 32-bit, ini berarti bahwa sesuatu di urutan 1/1000 dari satu persen pasangan input tidak meluap begitu saja.
sumber
pow(x,y)
tidak mengalir ke nol untuk sembarang x jika | y | <= 1. Ada pita input yang sangat sempit (besar x, y hampir -1) yang menyebabkan underflow, tetapi hasilnya masih berarti dalam kisaran itu.pow
hanyalah tabel pencarian kecil. :-)Karena tidak ada cara untuk merepresentasikan semua pangkat integer dalam int:
sumber
int pow(int base, unsigned int exponent)
danfloat pow(int base, int exponent)
int pow(int base, unsigned char exponent)
itu agak tidak berguna. Entah basisnya adalah 0, atau 1, dan eksponennya tidak penting, itu -1, dalam hal ini hanya eksponen bit terakhir yang penting, ataubase >1 || base< -1
dalam kasus iniexponent<256
pada penalti luapan.Itu sebenarnya pertanyaan yang menarik. Satu argumen yang belum saya temukan dalam diskusi adalah kurangnya nilai pengembalian yang jelas untuk argumen tersebut. Mari kita hitung bagaimana
int pow_int(int, int)
fungsi hypthetical bisa gagal.pow_int(0,0)
pow_int(2,-1)
Fungsi ini memiliki setidaknya 2 mode kegagalan. Bilangan bulat tidak dapat mewakili nilai-nilai ini, perilaku fungsi dalam kasus ini perlu ditentukan oleh standar - dan pemrogram perlu mengetahui bagaimana sebenarnya fungsi tersebut menangani kasus-kasus ini.
Secara keseluruhan, meninggalkan fungsi sepertinya satu-satunya pilihan yang masuk akal. Programmer dapat menggunakan versi floating point dengan semua pelaporan kesalahan tersedia.
sumber
pow
pelampung di antara juga? Ambil dua pelampung besar, naikkan satu dengan kekuatan yang lain dan Anda memiliki Overflow. Danpow(0.0, 0.0)
akan menyebabkan masalah yang sama seperti poin ke-2 Anda. Poin ketiga Anda adalah satu-satunya perbedaan nyata antara menerapkan fungsi daya untuk integer vs float.Jawaban singkat:
Spesialisasi
pow(x, n)
ke manan
bilangan asli sering berguna untuk kinerja waktu . Tetapi generik pustaka standarpow()
masih berfungsi dengan baik ( mengejutkan! ) Untuk tujuan ini dan sangat penting untuk menyertakan sesedikit mungkin dalam pustaka C standar sehingga dapat dibuat portabel dan semudah mungkin diimplementasikan. Di sisi lain, itu tidak menghentikannya sama sekali dari berada di pustaka standar C ++ atau STL, yang saya cukup yakin tidak ada yang berencana menggunakannya dalam beberapa jenis platform tertanam.Sekarang, untuk jawaban panjangnya.
pow(x, n)
dalam banyak kasus dapat dibuat lebih cepat dengan mengkhususkan dirin
pada bilangan asli. Saya harus menggunakan implementasi saya sendiri dari fungsi ini untuk hampir setiap program yang saya tulis (tetapi saya menulis banyak program matematika dalam C). Operasi khusus dapat dilakukanO(log(n))
tepat waktu, tetapi bilan
kecil, versi linier yang lebih sederhana dapat lebih cepat. Berikut implementasi keduanya:(Saya pergi
x
dan nilai kembaliannya berlipat ganda karena hasil daripow(double x, unsigned n)
akan masuk ganda seseringpow(double, double)
mungkin.)(Ya,
pown
itu rekursif, tetapi memecahkan tumpukan sama sekali tidak mungkin karena ukuran tumpukan maksimum kira-kira akan samalog_2(n)
dann
merupakan bilangan bulat. Jikan
adalah bilangan bulat 64-bit, itu memberi Anda ukuran tumpukan maksimum sekitar 64. Tidak ada perangkat keras yang sedemikian ekstrim keterbatasan memori, kecuali untuk beberapa PIC cerdik dengan tumpukan perangkat keras yang hanya melakukan panggilan fungsi 3 hingga 8 dalam.)Mengenai kinerja, Anda akan terkejut dengan kemampuan berbagai taman
pow(double, double)
. Saya menguji seratus juta iterasi pada IBM Thinkpad saya yang berusia 5 tahun denganx
angka iterasi yangn
sama dan sama dengan 10. Dalam skenario ini,pown_l
menang. glibcpow()
membutuhkan 12,0 detik pengguna,pown
7,4 detik pengguna, danpown_l
hanya 6,5 detik pengguna. Jadi itu tidak terlalu mengejutkan. Kami kurang lebih mengharapkan ini.Kemudian, saya biarkan
x
konstan (saya setel menjadi 2,5), dan saya mengulangn
dari 0 hingga 19 seratus juta kali. Kali ini, secara tidak terduga, glibcpow
menang, dan dengan telak! Hanya butuh 2.0 detik pengguna. Sayapown
mengambil 9,6 detik, danpown_l
mengambil 12,2 detik. Apa yang terjadi disini? Saya melakukan tes lain untuk mencari tahu.Saya melakukan hal yang sama seperti di atas hanya dengan
x
setara dengan satu juta. Kali ini,pown
menang di 9,6s.pown_l
mengambil 12.2s dan glibc pow mengambil 16.3s. Sekarang, sudah jelas! glibcpow
berkinerja lebih baik daripada ketiganya saatx
rendah, tetapi terburuk saatx
tinggi. Saatx
tinggi,pown_l
berkinerja terbaik saatn
rendah, danpown
berkinerja terbaik saatx
tinggi.Jadi, inilah tiga algoritme berbeda, masing-masing mampu berkinerja lebih baik daripada yang lain dalam situasi yang tepat. Jadi, pada akhirnya, mana yang akan digunakan kemungkinan besar tergantung pada bagaimana Anda berencana menggunakan
pow
, tetapi menggunakan versi yang tepat itu sepadan, dan memiliki semua versi itu bagus. Bahkan, Anda bahkan dapat mengotomatiskan pilihan algoritme dengan fungsi seperti ini:Selama
x_expected
dann_expected
merupakan konstanta yang diputuskan pada waktu kompilasi, bersama dengan kemungkinan beberapa peringatan lainnya, kompiler pengoptimalan yang bernilai garamnya akan secara otomatis menghapus seluruhpown_auto
pemanggilan fungsi dan menggantinya dengan pilihan yang sesuai dari ketiga algoritme. (Sekarang, jika Anda benar-benar akan mencoba menggunakan ini, Anda mungkin harus sedikit bermain-main dengannya, karena saya tidak benar-benar mencoba menyusun apa yang saya tulis di atas.;))Di sisi lain, glibc
pow
berfungsi dan glibc sudah cukup besar. Standar C seharusnya portabel, termasuk ke berbagai perangkat yang disematkan (pada kenyataannya pengembang yang disematkan di mana-mana umumnya setuju bahwa glibc sudah terlalu besar untuk mereka), dan itu tidak bisa portabel jika untuk setiap fungsi matematika sederhana perlu menyertakan setiap algoritma alternatif yang mungkin bisa digunakan. Jadi, itulah mengapa tidak ada dalam standar C.catatan kaki: Dalam pengujian kinerja waktu, saya memberikan fungsi saya tanda pengoptimalan yang relatif murah hati (
-s -O2
) yang cenderung sebanding dengan, jika tidak lebih buruk dari, apa yang mungkin digunakan untuk mengkompilasi glibc di sistem saya (archlinux), jadi hasilnya mungkin adil. Untuk tes yang lebih ketat, aku harus mengkompilasi glibc diri saya dan saya reeeally tidak merasa seperti melakukan hal itu. Saya dulu menggunakan Gentoo, jadi saya ingat berapa lama waktu yang dibutuhkan, bahkan ketika tugasnya otomatis . Hasilnya cukup meyakinkan (atau agak tidak meyakinkan) bagi saya. Anda tentu saja dipersilakan untuk melakukannya sendiri.Putaran bonus: Spesialisasi dari
pow(x, n)
semua bilangan bulat sangat penting jika diperlukan keluaran bilangan bulat yang tepat, yang memang terjadi. Pertimbangkan mengalokasikan memori untuk larik berdimensi-N dengan elemen p ^ N. Mendapatkan p ^ N off bahkan satu akan menghasilkan kemungkinan segfault yang terjadi secara acak.sumber
n
. godbolt.org/z/L9Kb98 . gcc dan clang gagal mengoptimalkan definisi rekursif Anda menjadi loop sederhana, dan sebenarnya melakukan branch pada setiap bitn
. (Karenapown_iter(double,unsigned)
mereka masih bercabang, tetapi implementasi SSE2 atau SSE4.1 tanpa cabang harus dimungkinkan di x86 asm atau dengan intrinsik C. Tetapi bahkan itu lebih baik daripada rekursi)Salah satu alasan C ++ agar tidak memiliki kelebihan beban tambahan adalah agar kompatibel dengan C.
C ++ 98 memiliki fungsi seperti
double pow(double, int)
, tetapi ini telah dihapus di C ++ 11 dengan argumen bahwa C99 tidak menyertakannya.http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550
Mendapatkan hasil yang sedikit lebih akurat juga berarti mendapatkan hasil yang sedikit berbeda .
sumber
Dunia terus berkembang dan begitu pula bahasa pemrogramannya. Bagian keempat dari desimal C TR ¹ menambahkan beberapa fungsi lagi
<math.h>
. Dua kelompok fungsi ini mungkin menarik untuk pertanyaan ini:pown
fungsi, yang mengambil sejumlah floating point danintmax_t
eksponen.powr
fungsi, yang mengambil dua poin angka floating (x
dany
) dan menghitungx
dengan kekuatany
dengan rumusexp(y*log(x))
.Tampaknya orang-orang standar pada akhirnya menganggap fitur-fitur ini cukup berguna untuk diintegrasikan ke dalam pustaka standar. Namun, rasionalnya adalah bahwa fungsi ini direkomendasikan oleh standar ISO / IEC / IEEE 60559: 2011 untuk bilangan floating point biner dan desimal. Saya tidak dapat mengatakan dengan pasti "standar" apa yang diikuti pada saat C89, tetapi evolusi masa depan
<math.h>
mungkin akan sangat dipengaruhi oleh evolusi masa depan ISO / IEC / IEEE 60559 .Perhatikan bahwa bagian keempat dari TR desimal tidak akan disertakan dalam C2x (revisi C mayor berikutnya), dan mungkin akan disertakan nanti sebagai fitur opsional. Belum ada niat apa pun yang saya ketahui untuk menyertakan bagian TR ini dalam revisi C ++ mendatang.
¹ Anda dapat menemukan beberapa dokumentasi pekerjaan yang sedang berlangsung di sini .
sumber
pown
dengan eksponen lebih besar dari yangLONG_MAX
seharusnya menghasilkan nilai yang berbeda dari penggunaanLONG_MAX
, atau di mana nilai yang lebih kecil dariLONG_MIN
seharusnya menghasilkan nilai yang berbedaLONG_MIN
? Saya ingin tahu manfaat apa yang didapat dari menggunakanintmax_t
eksponen?Mungkin karena ALU prosesor tidak mengimplementasikan fungsi seperti itu untuk bilangan bulat, tetapi ada instruksi FPU seperti itu (seperti yang ditunjukkan Stephen, sebenarnya ini adalah pasangan). Jadi sebenarnya lebih cepat untuk melakukan cast menjadi double, memanggil kekuatan dengan double, lalu menguji overflow dan cast back, daripada menerapkannya menggunakan aritmatika integer.
(untuk satu hal, logaritma mengurangi pangkat menjadi perkalian, tetapi logaritma bilangan bulat kehilangan banyak akurasi untuk sebagian besar input)
Stephen benar bahwa pada prosesor modern ini tidak lagi benar, tetapi standar C ketika fungsi matematika dipilih (C ++ baru saja menggunakan fungsi C) sekarang berapa, 20 tahun?
sumber
pow
. x86 memiliki sebuahy log2 x
instruksi (fyl2x
) yang dapat digunakan sebagai bagian pertama dari sebuahpow
fungsi, tetapi sebuahpow
fungsi yang ditulis dengan cara tersebut membutuhkan ratusan siklus untuk dieksekusi pada perangkat keras saat ini; rutinitas eksponensial integer yang ditulis dengan baik beberapa kali lebih cepat.Berikut adalah implementasi O (log (n)) yang sangat sederhana dari pow () yang bekerja untuk semua tipe numerik, termasuk integer :
Ini lebih baik daripada implementasi O (log (n)) enigmaticPhysicist karena tidak menggunakan rekursi.
Ini juga hampir selalu lebih cepat daripada implementasi liniernya (selama p> ~ 3) karena:
sumber
Faktanya, memang demikian.
Sejak C ++ 11 ada implementasi template dari
pow(int, int)
--- dan bahkan kasus yang lebih umum, lihat (7) di http://en.cppreference.com/w/cpp/numeric/math/powEDIT: para puritan mungkin berpendapat ini tidak benar, karena sebenarnya ada pengetikan "yang dipromosikan" yang digunakan. Dengan satu atau lain cara, seseorang mendapatkan
int
hasil yang benar , atau kesalahan, padaint
parameter.sumber
pow ( Arithmetic1 base, Arithmetic2 exp )
yang akan dilemparkan kedouble
ataulong double
jika Anda telah membaca deskripsi: "7) Satu set kelebihan beban atau template fungsi untuk semua kombinasi argumen jenis aritmatika tidak tercakup oleh 1-3). Jika ada argumen memiliki tipe integral, itu diubah menjadi ganda. Jika ada argumen yang panjangnya dobel, maka tipe kembalian Dipromosikan juga ganda panjang, jika tidak tipe kembaliannya selalu ganda. "pow(1.5f, 3)
=1072693280
butpow(1.5f, float(3))
=3.375
int pow(int, int)
, tetapi C ++ 11 hanya menyediakandouble pow(int, int)
. Lihat penjelasan @phuclv.Alasan yang sangat sederhana:
Segala sesuatu di perpustakaan STL didasarkan pada hal-hal paling akurat dan kuat yang bisa dibayangkan. Tentu, int akan kembali ke nol (dari 1/25) tetapi ini akan menjadi jawaban yang tidak akurat.
Saya setuju, itu aneh dalam beberapa kasus.
sumber