Saya mencari cara tercepat untuk menentukan apakah suatu long
nilai adalah kuadrat sempurna (yaitu akar kuadratnya adalah bilangan bulat lain):
- Saya telah melakukannya dengan cara mudah, dengan menggunakan
Math.sqrt()
fungsi bawaan, tetapi saya bertanya-tanya apakah ada cara untuk melakukannya lebih cepat dengan membatasi diri Anda ke domain integer-only. - Mempertahankan tabel pencarian tidak praktis (karena ada sekitar 2 31,5 bilangan bulat yang kuadratnya kurang dari 2 63 ).
Inilah cara yang sangat sederhana dan mudah yang saya lakukan sekarang:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Catatan: Saya menggunakan fungsi ini di banyak masalah Project Euler . Jadi tidak ada orang lain yang harus mempertahankan kode ini. Dan optimasi mikro semacam ini sebenarnya bisa membuat perbedaan, karena bagian dari tantangannya adalah melakukan setiap algoritma dalam waktu kurang dari satu menit, dan fungsi ini perlu disebut jutaan kali dalam beberapa masalah.
Saya sudah mencoba berbagai solusi untuk masalah ini:
- Setelah pengujian menyeluruh, saya menemukan bahwa menambahkan
0.5
ke hasil Math.sqrt () tidak diperlukan, setidaknya tidak pada mesin saya. - The cepat terbalik akar kuadrat lebih cepat, tapi itu memberi hasil yang salah untuk n> = 410881. Namun, seperti yang disarankan oleh BobbyShaftoe , kita dapat menggunakan hack FISR untuk n <410.881.
- Metode Newton sedikit lebih baik daripada
Math.sqrt()
. Ini mungkin karenaMath.sqrt()
menggunakan sesuatu yang mirip dengan Metode Newton, tetapi diterapkan pada perangkat keras sehingga jauh lebih cepat daripada di Jawa. Juga, Metode Newton masih membutuhkan penggunaan ganda. - Metode Newton yang dimodifikasi, yang menggunakan beberapa trik sehingga hanya matematika bilangan bulat yang terlibat, diperlukan beberapa peretasan untuk menghindari overflow (Saya ingin fungsi ini bekerja dengan semua bilangan bulat bertanda positif 64-bit), dan masih lebih lambat daripada
Math.sqrt()
. - Chop biner bahkan lebih lambat. Ini masuk akal karena memotong biner rata-rata akan memerlukan 16 lintasan untuk menemukan akar kuadrat dari angka 64-bit.
- Menurut tes John, menggunakan
or
pernyataan lebih cepat di C ++ daripada menggunakan aswitch
, tetapi di Jawa dan C # tampaknya tidak ada perbedaan antaraor
danswitch
. - Saya juga mencoba membuat tabel pencarian (sebagai array statis privat dari nilai 64 boolean). Maka alih-alih beralih atau
or
pernyataan, saya hanya akan mengatakanif(lookup[(int)(n&0x3F)]) { test } else return false;
. Yang mengejutkan saya, ini (hanya sedikit) lebih lambat. Ini karena batas array diperiksa di Jawa .
((1<<(n&15))|65004) != 0
, daripada melakukan tiga pemeriksaan terpisah.Jawaban:
Saya menemukan metode yang bekerja ~ 35% lebih cepat dari kode 6bit + Carmack + sqrt Anda, setidaknya dengan CPU saya (x86) dan bahasa pemrograman (C / C ++). Hasil Anda dapat bervariasi, terutama karena saya tidak tahu bagaimana faktor Java akan dimainkan.
Pendekatan saya ada tiga:
int64 x
.)z = r - x * x
, dan mengatur t menjadi kekuatan terbesar dari 2 z membagi dengan sedikit trik. Ini memungkinkan saya untuk melewatkan nilai t yang tidak akan memengaruhi nilai r. Nilai awal yang dikomputasi dalam kasus saya memilih modulo 8192 akar kuadrat "positif terkecil".Bahkan jika kode ini tidak bekerja lebih cepat untuk Anda, saya harap Anda menikmati beberapa ide yang terkandung di dalamnya. Kode lengkap dan teruji mengikuti, termasuk tabel prakomputasi.
sumber
9 < 0 => false
,9&2 => 0
,9&7 == 5 => false
,9&11 == 8 => false
.Saya sangat terlambat ke pesta, tetapi saya berharap dapat memberikan jawaban yang lebih baik; lebih pendek dan (dengan asumsi tolok ukur saya benar) juga jauh lebih cepat .
Tes pertama menangkap sebagian besar non-kotak dengan cepat. Ini menggunakan tabel 64-item yang dikemas dalam panjang, jadi tidak ada biaya akses array (tipuan dan cek batas). Untuk acak yang seragam
long
, ada kemungkinan 81,25% untuk berakhir di sini.Tes kedua menangkap semua angka yang memiliki jumlah ganjil dari dua faktorisasi mereka. Metode
Long.numberOfTrailingZeros
ini sangat cepat karena mendapat JIT-ed menjadi instruksi i86 tunggal.Setelah menjatuhkan nol yang tertinggal, tes ketiga menangani angka yang berakhiran 011, 101, atau 111 dalam biner, yang bukan kotak yang sempurna. Ini juga peduli tentang angka negatif dan juga menangani 0.
Tes akhir jatuh kembali ke
double
aritmatika. Karenadouble
hanya memiliki 53 bit mantissa, konversi darilong
menjadidouble
pembulatan untuk nilai besar. Meskipun demikian, tes ini benar (kecuali buktinya salah).Mencoba memasukkan ide mod255 tidak berhasil.
sumber
goodMask
uji melakukannya, tetapi melakukannya sebelum pergeseran yang tepat. Jadi Anda harus mengulanginya, tetapi dengan cara ini lebih sederhana dan AFAIK sedikit lebih cepat dan sama-sama bagus.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Anda harus melakukan pembandingan. Algoritma terbaik akan tergantung pada distribusi input Anda.
Algoritme Anda mungkin hampir optimal, tetapi Anda mungkin ingin melakukan pemeriksaan cepat untuk mengesampingkan beberapa kemungkinan sebelum memanggil rutin akar kuadrat Anda. Sebagai contoh, lihat digit terakhir nomor Anda dalam hex dengan melakukan sedikit-bijaksana "dan." Kuadrat sempurna hanya bisa berakhir pada 0, 1, 4, atau 9 di basis 16, Jadi untuk 75% dari input Anda (dengan asumsi mereka terdistribusi secara merata) Anda dapat menghindari panggilan ke root kuadrat dengan imbalan sedikit twiddling yang sangat cepat.
Kip membandingkan kode berikut ini dengan mengimplementasikan trik heks. Saat menguji angka 1 hingga 100.000.000, kode ini berlari dua kali lebih cepat dari aslinya.
Ketika saya menguji kode analog dalam C ++, itu sebenarnya berjalan lebih lambat dari aslinya. Namun, ketika saya menghilangkan pernyataan switch, trik hex sekali lagi membuat kode dua kali lebih cepat.
Menghilangkan pernyataan switch tidak banyak berpengaruh pada kode C #.
sumber
Saya berpikir tentang saat-saat mengerikan yang saya habiskan dalam kursus Analisis Numerik.
Dan kemudian saya ingat, ada fungsi ini berputar-putar di sekitar 'net dari kode Sumber Quake:
Yang pada dasarnya menghitung akar kuadrat, menggunakan fungsi perkiraan Newton (tidak ingat nama persisnya).
Itu harus dapat digunakan dan bahkan mungkin lebih cepat, itu dari salah satu permainan perangkat lunak id fenomenal!
Ini ditulis dalam C ++ tetapi seharusnya tidak terlalu sulit untuk menggunakan kembali teknik yang sama di Jawa setelah Anda mendapatkan ide:
Saya awalnya menemukannya di: http://www.codemaestro.com/reviews/9
Metode Newton dijelaskan di wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method
Anda dapat mengikuti tautan untuk penjelasan lebih lanjut tentang cara kerjanya, tetapi jika Anda tidak terlalu peduli, maka inilah kira-kira yang saya ingat dari membaca blog dan dari mengambil kursus Analisis Numerik:
* (long*) &y
dasarnya adalah fungsi convert-to-long yang cepat sehingga operasi integer dapat diterapkan pada byte mentah.0x5f3759df - (i >> 1);
garis adalah nilai benih pra-dihitung untuk fungsi pendekatan.* (float*) &i
mengubah nilai kembali ke floating point.y = y * ( threehalfs - ( x2 * y * y ) )
garis bascially iterates nilai alih fungsi lagi.Fungsi aproksimasi memberikan nilai yang lebih tepat semakin banyak Anda mengulangi fungsi dibandingkan hasilnya. Dalam kasus Quake, satu iterasi adalah "cukup baik", tetapi jika itu bukan untuk Anda ... maka Anda dapat menambahkan sebanyak iterasi yang Anda butuhkan.
Ini harus lebih cepat karena mengurangi jumlah operasi divisi yang dilakukan dalam rooting naif menjadi pembagian sederhana dengan 2 (sebenarnya
* 0.5F
operasi multiply) dan menggantinya dengan beberapa operasi multiplikasi sebagai gantinya.sumber
Saya tidak yakin apakah itu akan lebih cepat, atau bahkan akurat, tetapi Anda dapat menggunakan algoritma Magical Square Root dari John Carmack , untuk memecahkan akar kuadrat lebih cepat. Anda mungkin dapat dengan mudah menguji ini untuk semua kemungkinan integer 32 bit, dan memvalidasi bahwa Anda benar-benar mendapatkan hasil yang benar, karena itu hanya appoximation. Namun, sekarang saya berpikir tentang hal itu, menggunakan ganda hampir sama, jadi saya tidak yakin bagaimana itu akan ikut bermain.
sumber
Jika Anda melakukan pemotongan biner untuk mencoba menemukan akar kuadrat "benar", Anda dapat dengan mudah mendeteksi jika nilai yang Anda miliki cukup dekat untuk mengatakan:
Jadi setelah dihitung
n^2
, opsinya adalah:n^2 = target
: selesai, kembali benarn^2 + 2n + 1 > target > n^2
: Anda dekat, tetapi itu tidak sempurna: return falsen^2 - 2n + 1 < target < n^2
: dittotarget < n^2 - 2n + 1
: memotong biner di bagian bawahn
target > n^2 + 2n + 1
: memotong biner pada yang lebih tinggin
(Maaf, ini digunakan
n
sebagai tebakan Anda saat ini, dantarget
untuk parameter. Mohon maaf atas kebingungan!)Saya tidak tahu apakah ini akan lebih cepat atau tidak, tetapi patut dicoba.
EDIT: Chop biner tidak harus mengambil seluruh jajaran bilangan bulat,
(2^x)^2 = 2^(2x)
jadi, begitu Anda telah menemukan bit set teratas di target Anda (yang dapat dilakukan dengan trik sedikit-twiddling; saya lupa persis bagaimana) Anda dapat dengan cepat mendapatkan berbagai jawaban potensial. Pikiran Anda, memotong biner naif masih hanya akan memakan waktu hingga 31 atau 32 iterasi.sumber
Saya menjalankan analisis saya sendiri dari beberapa algoritma di utas ini dan menghasilkan beberapa hasil baru. Anda dapat melihat hasil lama dalam riwayat edit jawaban ini, tetapi hasilnya tidak akurat, karena saya membuat kesalahan, dan membuang-buang waktu menganalisis beberapa algoritma yang tidak dekat. Namun, menarik pelajaran dari beberapa jawaban yang berbeda, saya sekarang memiliki dua algoritma yang menghancurkan "pemenang" utas ini. Inilah hal inti yang saya lakukan berbeda dari orang lain:
Namun, baris sederhana ini, yang sebagian besar waktu menambahkan satu atau dua instruksi yang sangat cepat, sangat menyederhanakan
switch-case
pernyataan menjadi satu jika pernyataan. Namun, ini dapat menambah runtime jika banyak angka yang diuji memiliki kekuatan dua faktor yang signifikan.Algoritma di bawah ini adalah sebagai berikut:
Berikut ini adalah contoh runtime jika angka-angka tersebut dihasilkan menggunakan
Math.abs(java.util.Random.nextLong())
Dan berikut ini contoh runtime jika dijalankan hanya pada satu juta long pertama:
Seperti yang Anda lihat,
DurronTwo
melakukan lebih baik untuk input besar, karena bisa menggunakan trik sulap sangat sering, tetapi akan musnah dibandingkan dengan algoritma pertama danMath.sqrt
karena jumlahnya jauh lebih kecil. Sementara itu, yang lebih simpelDurron
adalah pemenang yang sangat besar karena tidak pernah harus membelah sebanyak 4 kali lipat dalam jutaan angka pertama.Inilah
Durron
:Dan
DurronTwo
Dan harness benchmark saya: (Membutuhkan Google caliper 0.1-rc5)
UPDATE: Saya telah membuat algoritma baru yang lebih cepat dalam beberapa skenario, lebih lambat dalam skenario lain, saya mendapatkan tolok ukur yang berbeda berdasarkan input yang berbeda. Jika kita menghitung modulo
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, kita bisa menghilangkan 97,82% dari angka yang tidak bisa kuadrat. Ini dapat (semacam) dilakukan dalam satu baris, dengan 5 operasi bitwise:Indeks yang dihasilkan adalah 1) residu, 2) residu
+ 0xFFFFFF
, atau 3) residu+ 0x1FFFFFE
. Tentu saja, kita perlu memiliki tabel pencarian untuk residu modulo0xFFFFFF
, yaitu sekitar 3mb file (dalam hal ini disimpan sebagai angka desimal teks ascii, tidak optimal tetapi jelas tidak dapat diperbaiki dengan aByteBuffer
dan sebagainya. Tapi karena itu perhitungan awal tidak jadi ' sangat penting. Anda dapat menemukan file di sini (atau menghasilkan sendiri):Saya memuatnya ke dalam
boolean
array seperti ini:Contoh runtime. Itu mengalahkan
Durron
(versi satu) di setiap percobaan saya berlari.sumber
sqrtps
throughput SIMD atau bahkansqrtpd
(presisi ganda) tidak terlalu buruk pada Skylake, tetapi tidak jauh lebih baik daripada latensi pada CPU lama. Pokoknya 7-cpu.com/cpu/Haswell.html memiliki beberapa nomor percobaan yang bagus, dan halaman untuk CPU lainnya. Panduan mikroarch Agner Fog pdf memiliki beberapa nomor latensi cache untuk Intel dan AMD uarches: agner.org/optimizedouble
ketelitian untuk menghindari pembulatan bilangan bulat di luar kisaran + -2 ^ 24 (sehingga bilangan bulat 32-bit bisa berada di luar itu), dansqrtpd
lebih lambat daripadasqrtps
serta hanya memproses setengah elemen sebanyak per elemen (per vektor SIMD) .Seharusnya lebih cepat menggunakan metode Newton untuk menghitung Integer Square Root , lalu kuadratkan angka ini dan periksa, seperti yang Anda lakukan dalam solusi Anda saat ini. Metode Newton adalah dasar untuk solusi Carmack yang disebutkan dalam beberapa jawaban lain. Anda harus bisa mendapatkan jawaban yang lebih cepat karena Anda hanya tertarik pada bagian integer dari root, memungkinkan Anda untuk menghentikan algoritma aproksimasi lebih awal.
Pengoptimalan lain yang dapat Anda coba: Jika Digital Root suatu angka tidak berakhir pada 1, 4, 7, atau 9 angka itu bukan kuadrat sempurna. Ini dapat digunakan sebagai cara cepat untuk menghilangkan 60% dari input Anda sebelum menerapkan algoritma root square yang lebih lambat.
sumber
Math.sqrt()
bekerja dengan ganda sebagai parameter input, sehingga Anda tidak akan mendapatkan hasil yang akurat untuk bilangan bulat lebih besar dari 2 ^ 53 .sumber
Sebagai catatan, pendekatan lain adalah menggunakan dekomposisi utama. Jika setiap faktor dekomposisi genap, maka angkanya adalah kuadrat sempurna. Jadi yang Anda inginkan adalah melihat apakah suatu angka dapat diuraikan sebagai produk kuadrat bilangan prima. Tentu saja, Anda tidak perlu mendapatkan dekomposisi seperti itu, hanya untuk melihat apakah itu ada.
Pertama-tama buatlah sebuah kotak kuadrat dari bilangan prima yang lebih rendah dari 2 ^ 32. Ini jauh lebih kecil dari tabel semua bilangan bulat hingga batas ini.
Sebuah solusi akan menjadi seperti ini:
Saya kira itu agak samar. Apa yang dilakukannya adalah memeriksa di setiap langkah bahwa kuadrat dari bilangan prima membagi nomor input. Jika tidak maka itu membagi angka dengan kuadrat selama mungkin, untuk menghapus kuadrat ini dari dekomposisi utama. Jika dengan proses ini, kita sampai ke 1, maka nomor input adalah dekomposisi kuadrat dari bilangan prima. Jika kuadrat menjadi lebih besar dari angka itu sendiri, maka tidak mungkin kuadrat ini, atau kuadrat yang lebih besar, dapat membaginya, sehingga angka tersebut tidak dapat menjadi dekomposisi kuadrat dari bilangan prima.
Mengingat sqrt saat ini dilakukan dalam perangkat keras dan kebutuhan untuk menghitung bilangan prima di sini, saya kira solusi ini jauh lebih lambat. Tetapi harus memberikan hasil yang lebih baik daripada solusi dengan sqrt yang tidak akan bekerja lebih dari 2 ^ 54, seperti kata mrzl dalam jawabannya.
sumber
sqrtsd
throughput Core2 adalah satu per 6-58c. Iniidiv
adalah satu per 12-36 sepeda. (latensi mirip dengan throughput: tidak ada unit yang disalin).Telah ditunjukkan bahwa
d
digit terakhir dari bujur sangkar sempurna hanya dapat mengambil nilai-nilai tertentu.d
Digit terakhir (dalam basisb
) dari angkan
sama dengan sisa ketikan
dibagi denganb
d
, yaitu. dalam notasi Cn % pow(b, d)
.Ini dapat digeneralisasi ke modulus apa pun
m
, yaitu.n % m
dapat digunakan untuk mengesampingkan beberapa persentase angka dari menjadi kuadrat sempurna. Modulus yang Anda gunakan saat ini adalah 64, yang memungkinkan 12, yaitu. 19% dari sisa, kotak mungkin. Dengan sedikit pengkodean saya menemukan modulus 110880, yang memungkinkan hanya 2016, yaitu. 1,8% dari sisa kotak mungkin. Jadi tergantung pada biaya operasi modulus (mis. Divisi) dan pencarian tabel versus akar kuadrat pada mesin Anda, menggunakan modulus ini mungkin lebih cepat.Omong-omong jika Java memiliki cara untuk menyimpan array bit yang dikemas untuk tabel pencarian, jangan gunakan itu. 110880 kata 32-bit tidak banyak RAM hari ini dan mengambil kata mesin akan lebih cepat daripada mengambil sedikit pun.
sumber
idiv
) sama atau lebih buruk dalam biaya untuk FP sqrt (sqrtsd
) pada perangkat keras x86 saat ini. Juga, sama sekali tidak setuju dengan menghindari bitfield. Cache hit rate akan menjadi ton lebih baik dengan bitfield, dan pengujian bit di bitfield hanya satu atau dua instruksi lebih sederhana daripada menguji seluruh byte. (Untuk tabel kecil yang muat dalam cache bahkan sebagai non-bitfields, array byte akan lebih baik, bukan int 32bit. X86 memiliki akses byte tunggal dengan kecepatan yang sama dengan 32bit dword.)Masalah bilangan bulat layak mendapatkan solusi bilangan bulat. Jadi
Lakukan pencarian biner pada bilangan bulat (non-negatif) untuk menemukan t bilangan bulat terbesar sehingga
t**2 <= n
. Kemudian uji apakahr**2 = n
tepat. Ini membutuhkan waktu O (log n).Jika Anda tidak tahu cara mencari biner bilangan bulat positif karena set tidak terikat, itu mudah. Anda mulai dengan menghitung fungsi Anda yang meningkat f (di atas
f(t) = t**2 - n
) dengan kekuatan dua. Ketika Anda melihatnya berubah positif, Anda telah menemukan batas atas. Kemudian Anda dapat melakukan pencarian biner standar.sumber
O((log n)^2)
karena perkalian bukan waktu konstan tetapi sebenarnya memiliki batas yang lebih rendahO(log n)
, yang menjadi jelas ketika bekerja dengan angka multi-presisi yang besar. Tetapi ruang lingkup wiki ini tampaknya 64-bit, jadi mungkin itu nbd.Penyederhanaan solusi maaartinus berikut ini tampaknya mengurangi beberapa poin persentase dari runtime, tapi saya tidak cukup baik dalam membuat tolok ukur untuk menghasilkan tolok ukur yang dapat saya percayai:
Akan bermanfaat untuk memeriksa bagaimana menghilangkan tes pertama,
akan mempengaruhi kinerja.
sumber
Untuk kinerja, Anda seringkali harus melakukan beberapa kompromi. Yang lain telah mengungkapkan berbagai metode, namun, Anda mencatat hack Carmack lebih cepat hingga nilai-nilai N. tertentu. Kemudian, Anda harus memeriksa "n" dan jika kurang dari angka N itu, gunakan hack Carmack, atau gunakan beberapa metode lain yang dijelaskan dalam jawaban di sini.
sumber
Ini adalah implementasi Java tercepat yang bisa saya buat, menggunakan kombinasi teknik yang disarankan oleh orang lain di utas ini.
Saya juga bereksperimen dengan modifikasi ini tetapi mereka tidak membantu kinerja:
sumber
Anda harus menyingkirkan bagian 2-daya N sejak awal.
Sunting ke-2 Ekspresi ajaib untuk m di bawah ini seharusnya
dan tidak seperti yang tertulis
Akhir dari edit ke-2
Edit 1:
Perbaikan kecil:
Akhir pengeditan 1
Sekarang lanjutkan seperti biasa. Dengan cara ini, pada saat Anda sampai ke bagian floating point, Anda sudah menyingkirkan semua angka yang bagian 2-kekuatannya ganjil (sekitar setengah), dan kemudian Anda hanya mempertimbangkan 1/8 dari apa yang tersisa. Yaitu Anda menjalankan bagian floating point pada 6% dari angka.
sumber
Project Euler disebutkan dalam tag dan banyak masalah di dalamnya memerlukan memeriksa nomor >>
2^64
. Sebagian besar optimasi yang disebutkan di atas tidak bekerja dengan mudah ketika Anda bekerja dengan buffer 80 byte.Saya menggunakan java BigInteger dan versi yang sedikit dimodifikasi dari metode Newton, yang berfungsi lebih baik dengan bilangan bulat. Masalahnya adalah bahwa kotak yang tepat
n^2
konvergen(n-1)
bukann
karenan^2-1 = (n-1)(n+1)
dan kesalahan terakhir hanya satu langkah di bawah pembagi akhir dan algoritma dihentikan. Mudah untuk memperbaikinya dengan menambahkan satu ke argumen asli sebelum menghitung kesalahan. (Tambahkan dua untuk akar pangkat tiga, dll.)Salah satu atribut bagus dari algoritma ini adalah Anda dapat segera mengetahui apakah angka tersebut adalah kuadrat sempurna - kesalahan terakhir (bukan koreksi) dalam metode Newton akan menjadi nol. Modifikasi sederhana juga memungkinkan Anda menghitung dengan cepat,
floor(sqrt(x))
bukan bilangan bulat terdekat. Ini berguna dengan beberapa masalah Euler.sumber
Ini pengerjaan ulang dari desimal ke biner dari algoritma kalkulator Marchant lama (maaf, saya tidak punya referensi), di Ruby, diadaptasi khusus untuk pertanyaan ini:
Berikut ini adalah hasil dari sesuatu yang serupa (tolong jangan pilih saya untuk gaya pengkodean / bau atau kikuk O / O - itu adalah algoritma yang diperhitungkan, dan C ++ bukan bahasa rumah saya). Dalam hal ini, kami sedang mencari residu == 0:
sumber
Panggilan sqrt tidak sepenuhnya akurat, seperti yang telah disebutkan, tetapi menarik dan bermanfaat bahwa itu tidak menerbangkan jawaban lain dalam hal kecepatan. Bagaimanapun, urutan instruksi bahasa rakitan untuk sqrt adalah kecil. Intel memiliki instruksi perangkat keras, yang tidak digunakan oleh Java, saya percaya karena tidak sesuai dengan IEEE.
Jadi mengapa ini lambat? Karena Java sebenarnya memanggil rutin C melalui JNI, dan itu sebenarnya lebih lambat untuk melakukannya daripada memanggil subrutin Java, yang itu sendiri lebih lambat daripada melakukannya secara inline. Ini sangat menjengkelkan, dan Java seharusnya memberikan solusi yang lebih baik, yaitu membangun panggilan pustaka floating point jika perlu. Baiklah.
Dalam C ++, saya menduga semua alternatif kompleks akan kehilangan kecepatan, tapi saya belum memeriksa semuanya. Apa yang saya lakukan, dan apa yang menurut orang Jawa bermanfaat, adalah peretasan sederhana, perpanjangan dari pengujian kasus khusus yang disarankan oleh A. Rex. Gunakan nilai panjang tunggal sebagai array bit, yang tidak dibatasi batasnya. Dengan begitu, Anda memiliki pencarian boolean 64 bit.
IsPerfectSquare5 rutin berjalan sekitar 1/3 waktu pada mesin duo core2 saya. Saya menduga bahwa tweak lebih lanjut sepanjang garis yang sama dapat mengurangi waktu rata-rata lebih jauh, tetapi setiap kali Anda memeriksa, Anda menukar lebih banyak pengujian untuk lebih menghilangkan, sehingga Anda tidak bisa pergi terlalu jauh di jalan itu.
Tentu saja, daripada memiliki tes terpisah untuk negatif, Anda dapat memeriksa 6 bit tinggi dengan cara yang sama.
Perhatikan bahwa semua yang saya lakukan adalah menghilangkan kotak yang mungkin, tetapi ketika saya memiliki kasus potensial saya harus memanggil yang asli, isPerfectSquare inline.
Rutin init2 dipanggil sekali untuk menginisialisasi nilai statis pp1 dan pp2. Perhatikan bahwa dalam implementasi saya di C ++, saya menggunakan unsigned lama, jadi sejak Anda masuk, Anda harus menggunakan operator >>>.
Tidak ada kebutuhan intrinsik untuk memeriksa batas array, tetapi pengoptimal Java harus memecahkan masalah ini dengan cepat, jadi saya tidak menyalahkan mereka untuk itu.
sumber
pp2
? Saya mengerti bahwapp1
ini digunakan untuk menguji enam bit paling tidak signifikan, tetapi saya tidak percaya bahwa menguji enam bit berikutnya masuk akal.Saya suka ide untuk menggunakan metode yang hampir benar pada beberapa input. Ini adalah versi dengan "offset" yang lebih tinggi. Kode ini sepertinya berfungsi dan lolos dari test case sederhana saya.
Cukup ganti:
kode dengan yang ini:
sumber
Mempertimbangkan panjang bit umum (meskipun saya telah menggunakan tipe spesifik di sini), saya mencoba merancang algo sederhana seperti di bawah ini. Diperlukan pemeriksaan sederhana dan jelas untuk 0,1,2 atau <0 pada awalnya. Mengikuti adalah sederhana dalam arti bahwa ia tidak mencoba menggunakan fungsi matematika yang ada. Sebagian besar operator dapat diganti dengan operator bit-wise. Saya belum diuji dengan data tanda bench. Saya bukan ahli dalam matematika atau desain algoritma komputer pada khususnya, saya akan senang melihat Anda menunjukkan masalah. Saya tahu ada banyak peluang peningkatan di sana.
sumber
Saya memeriksa semua hasil yang mungkin ketika n bit terakhir dari sebuah persegi diamati. Dengan berturut-turut memeriksa lebih banyak bit, hingga 5/6 input dapat dihilangkan. Saya sebenarnya merancang ini untuk mengimplementasikan algoritma Fermat's Factorization, dan sangat cepat di sana.
Bit pseudocode terakhir dapat digunakan untuk memperluas tes untuk menghilangkan lebih banyak nilai. Tes di atas adalah untuk k = 0, 1, 2, 3
Pertama-tama menguji apakah ia memiliki residu kuadrat dengan moduli kekuatan dua, kemudian tes berdasarkan modulus akhir, kemudian menggunakan Math.sqrt untuk melakukan tes akhir. Saya datang dengan ide dari jabatan teratas, dan berusaha memperluasnya. Saya menghargai komentar atau saran.
Pembaruan: Menggunakan tes dengan modulus, (modSq) dan basis modulus 44352, pengujian saya berjalan di 96% dari waktu yang ada di pembaruan OP untuk angka hingga 1.000.000.000.
sumber
Ini adalah solusi membagi dan menaklukkan.
Jika akar kuadrat dari angka alami (
number
) adalah angka alami (solution
), Anda dapat dengan mudah menentukan rentangsolution
berdasarkan pada jumlah digit darinumber
:number
memiliki 1 digit:solution
dalam kisaran = 1 - 4number
memiliki 2 digit:solution
dalam kisaran = 3 - 10number
memiliki 3 digit:solution
dalam kisaran = 10 - 40number
memiliki 4 digit:solution
dalam kisaran = 30 - 100number
memiliki 5 digit:solution
dalam kisaran = 100 - 400Perhatikan pengulangannya?
Anda dapat menggunakan rentang ini dalam pendekatan pencarian biner untuk melihat apakah ada
solution
yang:Ini kodenya
Ini adalah SquareRootChecker kelas saya
Dan berikut ini adalah contoh cara menggunakannya.
sumber
toString
adalah operasi yang sangat mahal dibandingkan dengan operator bitwise. Dengan demikian, untuk memenuhi tujuan dari pertanyaan - kinerja - Anda harus menggunakan operator bitwise bukannya string 10 basis. Sekali lagi, saya sangat menyukai konsep Anda. Meskipun demikian, implementasi Anda (seperti saat ini) sejauh ini adalah yang paling lambat dari semua solusi yang mungkin diposting untuk pertanyaan.Jika kecepatan menjadi perhatian, mengapa tidak mempartisi set input dan nilai-nilainya yang paling umum digunakan ke tabel pencarian dan kemudian melakukan algoritma sulap yang dioptimalkan untuk kasus luar biasa?
sumber
Seharusnya dimungkinkan untuk mengemas 'tidak bisa menjadi kuadrat sempurna jika angka X terakhir adalah N' jauh lebih efisien dari itu! Saya akan menggunakan java 32 bit ints, dan menghasilkan data yang cukup untuk memeriksa 16 bit terakhir dari nomor - itu 2048 nilai int heksadesimal.
...
Baik. Entah saya telah menemukan beberapa teori bilangan yang sedikit di luar saya, atau ada bug dalam kode saya. Bagaimanapun, ini kodenya:
dan inilah hasilnya:
(ed: elided untuk kinerja buruk di prettify.js; lihat riwayat revisi untuk dilihat.)
sumber
Metode Newton dengan aritmatika integer
Jika Anda ingin menghindari operasi non-integer, Anda dapat menggunakan metode di bawah ini. Ini pada dasarnya menggunakan Metode Newton yang dimodifikasi untuk integer aritmatika.
Implementasi ini tidak dapat bersaing dengan solusi yang digunakan
Math.sqrt
. Namun, kinerjanya dapat ditingkatkan dengan menggunakan mekanisme penyaringan yang dijelaskan dalam beberapa pos lainnya.sumber
Menghitung akar kuadrat dengan metode Newton sangat cepat ... asalkan nilai awalnya masuk akal. Namun tidak ada nilai awal yang masuk akal, dan dalam praktik kami diakhiri dengan perilaku membagi dua dan mencatat (2 ^ 64).
Agar benar-benar cepat, kita perlu cara cepat untuk mendapatkan nilai awal yang masuk akal, dan itu berarti kita perlu turun ke bahasa mesin. Jika sebuah prosesor memberikan instruksi seperti POPCNT di Pentium, yang menghitung nol terkemuka kita dapat menggunakannya untuk memiliki nilai awal dengan setengah bit signifikan. Dengan hati-hati kita dapat menemukan sejumlah langkah Newton yang pasti akan cukup. (Dengan demikian melepaskan kebutuhan untuk loop dan memiliki eksekusi yang sangat cepat.)
Solusi kedua akan melalui fasilitas floating point, yang mungkin memiliki perhitungan sqrt cepat (seperti coprocessor i87.) Bahkan perjalanan melalui exp () dan log () mungkin lebih cepat daripada Newton yang terdegenerasi menjadi pencarian biner. Ada aspek rumit untuk ini, analisis tergantung prosesor tentang apa dan jika perbaikan setelahnya diperlukan.
Solusi ketiga menyelesaikan masalah yang sedikit berbeda, tetapi perlu disebutkan karena situasinya dijelaskan dalam pertanyaan. Jika Anda ingin menghitung banyak akar kuadrat untuk angka-angka yang sedikit berbeda, Anda dapat menggunakan iterasi Newton, jika Anda tidak pernah menginisialisasi ulang nilai awal, tetapi biarkan saja di tempat perhitungan sebelumnya ditinggalkan. Saya telah menggunakan ini dengan sukses dalam setidaknya satu masalah Euler.
sumber
Akar Kuadrat dari angka, mengingat bahwa angka tersebut adalah kuadrat sempurna.
Kompleksitasnya adalah log (n)
sumber
Jika Anda menginginkan kecepatan, mengingat bilangan bulat Anda berukuran terbatas, saya menduga bahwa cara tercepat akan melibatkan (a) mempartisi parameter berdasarkan ukuran (misalnya, ke dalam kategori dengan set bit terbesar), kemudian memeriksa nilainya terhadap array kuadrat sempurna. dalam kisaran itu.
sumber
Mengenai metode Carmac, sepertinya akan cukup mudah hanya untuk mengulangi sekali lagi, yang seharusnya menggandakan jumlah digit akurasi. Bagaimanapun, ini adalah metode berulang yang sangat terpotong - metode Newton, dengan tebakan pertama yang sangat bagus.
Mengenai yang terbaik saat ini, saya melihat dua optimasi mikro:
Yaitu:
Bahkan yang lebih baik mungkin sederhana
Jelas, akan menarik untuk mengetahui berapa banyak angka yang diambil di setiap pos pemeriksaan - Saya agak ragu bahwa cek benar-benar independen, yang membuat semuanya rumit.
sumber