Apa cara yang paling efisien untuk membandingkan dua double
atau dua float
nilai?
Melakukan hal ini tidak benar:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Tetapi sesuatu seperti:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Tampaknya pengolahan limbah.
Adakah yang tahu komparator float yang lebih pintar?
<invoke Knuth>
Optimalisasi prematur adalah akar dari semua kejahatan.</invoke Knuth>
Pergilah dengan abs (ab) <EPS seperti disebutkan di atas, jelas dan mudah dimengerti.==
bisa sepenuhnya benar, tetapi ini sepenuhnya tergantung pada konteks yang tidak diberikan dalam pertanyaan. Sampai konteks itu diketahui,==
tetaplah "cara paling efisien" .Jawaban:
Berhati-hatilah dalam menggunakan saran lainnya. Itu semua tergantung konteks.
Saya telah menghabiskan waktu yang lama untuk melacak bug dalam sistem yang mungkin
a==b
jika|a-b|<epsilon
. Masalah yang mendasarinya adalah:Anggapan implisit dalam suatu algoritma bahwa jika
a==b
danb==c
kemudiana==c
.Menggunakan epsilon yang sama untuk garis yang diukur dalam inci dan garis yang diukur dalam mils (0,001 inci). Itulah
a==b
tapi1000a!=1000b
. (Inilah sebabnya AlmostEqual2sComplement meminta epsilon atau ULPS maks).Penggunaan epsilon yang sama untuk kosinus sudut dan panjang garis!
Menggunakan fungsi perbandingan untuk mengurutkan item dalam koleksi. (Dalam hal ini menggunakan operator C ++ bawaan == untuk ganda menghasilkan hasil yang benar.)
Seperti saya katakan: semuanya tergantung pada konteks dan ukuran yang diharapkan
a
danb
.BTW,
std::numeric_limits<double>::epsilon()
adalah "mesin epsilon". Ini adalah perbedaan antara 1,0 dan nilai selanjutnya yang diwakili oleh suatu ganda. Saya kira itu bisa digunakan dalam fungsi bandingkan tetapi hanya jika nilai yang diharapkan kurang dari 1. (Ini sebagai respons terhadap jawaban @ cdv ...)Juga, jika pada dasarnya Anda memiliki
int
aritmatikadoubles
( di sini kami menggunakan ganda untuk menyimpan nilai int dalam kasus-kasus tertentu) aritmatika Anda akan benar. Misalnya 4.0 / 2.0 akan sama dengan 1.0 + 1.0. Ini selama Anda tidak melakukan hal-hal yang menghasilkan pecahan (4.0 / 3.0) atau tidak melampaui ukuran int.sumber
fabs(a)+fabs(b)
tetapi dengan mengkompensasi NaN, 0 jumlah dan melimpah, ini menjadi sangat kompleks.float
/double
adalah mantissa x 2 ^ EXP .epsilon
akan tergantung pada eksponen. Sebagai contoh jika mantissa adalah 24bits dan eksponen ditandatangani 8bit, maka1/(2^24)*2^127
atau~2^103
adalahepsilon
untuk beberapa nilai; atau ini mengacu pada epsilon minimum ?|a-b|<epsilon
, itu tidak benar. Harap tambahkan tautan ini ke jawaban Anda; jika Anda setuju cygnus-software.com/papers/comparingfloats/comparingfloats.htm dan saya dapat menghapus komentar bodoh saya.Perbandingan dengan nilai epsilon adalah apa yang dilakukan kebanyakan orang (bahkan dalam pemrograman game).
Anda harus mengubah implementasi Anda sedikit:
Sunting: Christer telah menambahkan setumpuk info hebat tentang topik ini pada posting blog terbaru . Nikmati.
sumber
float a = 3.4; if(a == 3.4){...}
yaitu ketika Anda membandingkan floating point yang disimpan dengan | literal Dalam hal ini, kedua nomor disimpan, sehingga mereka akan memiliki representasi yang sama, jika sama, jadi apa salahnya melakukana == b
?EPSILON
didefinisikan sebagaiDBL_EPSILON
. Biasanya itu akan menjadi nilai spesifik yang dipilih tergantung pada keakuratan perbandingan yang diperlukan.EPSILON
perbandingan tidak berfungsi ketika mengapung besar, karena perbedaan antara mengapung berturut-turut juga menjadi besar. Lihat artikel ini .EPSILON
cukup banyak tidak berguna. Anda perlu membandingkan dengan ambang batas yang masuk akal untuk unit yang ada. Juga, gunakanstd::abs
karena kelebihan beban untuk tipe floating point yang berbeda.Saya menemukan bahwa Kerangka Pengujian Google C ++ berisi implementasi templat berbasis-platform bagus dari AlmostEqual2sComplement yang bekerja pada doubles dan float. Mengingat bahwa itu dirilis di bawah lisensi BSD, menggunakannya dalam kode Anda sendiri seharusnya tidak menjadi masalah, selama Anda mempertahankan lisensi tersebut. Saya mengekstrak kode di bawah ini dari
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h dan menambahkan lisensi di atas.Pastikan untuk #define GTEST_OS_WINDOWS ke beberapa nilai (atau untuk mengubah kode di mana ia digunakan untuk sesuatu yang sesuai dengan basis kode Anda - itu BSD dilisensikan setelah semua).
Contoh penggunaan:
Berikut kodenya:
EDIT: Posting ini berumur 4 tahun. Mungkin masih valid, dan kodenya bagus, tetapi beberapa orang menemukan peningkatan. Best go dapatkan versi terbaru
AlmostEquals
langsung dari kode sumber Google Test, dan bukan yang saya tempel di sini.sumber
Membandingkan angka floating point tergantung pada konteksnya. Karena bahkan mengubah urutan operasi dapat menghasilkan hasil yang berbeda, penting untuk mengetahui seberapa "sama" Anda inginkan angka-angkanya.
Membandingkan angka floating point oleh Bruce Dawson adalah tempat yang baik untuk memulai ketika melihat perbandingan floating point.
Definisi-definisi berikut berasal dari Seni pemrograman komputer oleh Knuth :
Tentu saja, memilih epsilon tergantung pada konteksnya, dan menentukan seberapa setara angka yang Anda inginkan.
Metode lain untuk membandingkan angka floating point adalah dengan melihat ULP (unit di tempat terakhir) dari angka-angka tersebut. Meskipun tidak berurusan secara khusus dengan perbandingan, makalah Apa yang harus diketahui oleh setiap ilmuwan komputer tentang angka floating point adalah sumber yang bagus untuk memahami bagaimana floating point bekerja dan apa jebakannya, termasuk apa ULP itu.
sumber
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
menyelamatkan hidupku. LOL Perhatikan bahwa versi ini (saya belum memeriksa apakah berlaku untuk yang lain juga) juga mempertimbangkan perubahan yang mungkin terjadi di bagian integral dari angka floating point (contoh: di2147352577.9999997616 == 2147352576.0000000000
mana Anda dapat dengan jelas melihat bahwa hampir ada perbedaan dari2
antara dua angka) yang cukup bagus! Ini terjadi ketika kesalahan pembulatan yang diakumulasikan melebihi bagian desimal dari angka tersebut.std::max(std::abs(a), std::abs(b))
(atau denganstd::min()
);std::abs
di C ++ kelebihan beban dengan float & double type, jadi berfungsi dengan baik (Anda selalu bisa menjagafabs
keterbacaannya).Untuk pendekatan yang lebih mendalam, baca Membandingkan angka floating point . Berikut ini cuplikan kode dari tautan itu:
sumber
*(int*)&A;
" melanggar aturan aliasing yang ketat?Menyadari ini adalah utas lama tetapi artikel ini adalah salah satu yang paling lurus ke depan yang saya temukan dalam membandingkan angka floating point dan jika Anda ingin menjelajahi lebih banyak ia memiliki referensi yang lebih rinci juga dan itu situs utama mencakup berbagai masalah lengkap berurusan dengan angka floating point Panduan Floating-Point: Perbandingan .
Kita dapat menemukan artikel yang agak lebih praktis dalam toleransi titik Teraput ditinjau kembali dan mencatat ada tes toleransi absolut , yang bermuara pada ini di C ++:
dan uji toleransi relatif :
Artikel tersebut mencatat bahwa tes absolut gagal ketika
x
dany
besar dan gagal dalam kasus relatif ketika mereka kecil. Dengan asumsi ia mutlak dan relatif toleran sama tes gabungan akan terlihat seperti ini:sumber
Cara portabel untuk mendapatkan epsilon di C ++ adalah
Maka fungsi perbandingan menjadi
sumber
Saya akhirnya menghabiskan cukup banyak waktu untuk menelusuri materi di utas hebat ini. Saya ragu semua orang ingin menghabiskan begitu banyak waktu sehingga saya akan menyoroti ringkasan dari apa yang saya pelajari dan solusi yang saya terapkan.
Ringkasan Cepat
numeric_limits::epsilon()
yang sama dengan FLT_EPSILON di float.h. Namun ini bermasalah karena epsilon untuk membandingkan nilai seperti 1.0 tidak sama dengan epsilon untuk nilai seperti 1E9. FLT_EPSILON didefinisikan untuk 1.0.fabs(a-b) <= epsilon
namun ini tidak berfungsi karena default epsilon didefinisikan untuk 1.0. Kita perlu meningkatkan epsilon ke atas atau ke bawah dalam hal a dan b.max(a,b)
atau Anda bisa mendapatkan angka representatif berikutnya di sekitar a dan kemudian melihat apakah b termasuk dalam kisaran itu. Yang pertama disebut metode "relatif" dan kemudian disebut metode ULP.Implementasi Fungsi Utilitas (C ++ 11)
sumber
isDefinitelyLessThan
cekdiff < tolerance
, yang berarti a dan b hampir sama (dan jadi tidak pasti kurang dari b). Bukankah lebih masuk akal untuk memeriksa toleransi yang berbeda di kedua kasus? Atau mungkin menambahkanorEqualTo
argumen yang mengontrol apakah perkiraan pemeriksaan kesetaraan harus kembali benar atau tidak.Kode yang Anda tulis disadap:
Kode yang benar adalah:
(... dan ya ini berbeda)
Saya ingin tahu apakah hebat tidak akan membuat Anda kehilangan evaluasi malas dalam beberapa kasus. Saya akan mengatakan itu tergantung pada kompiler. Anda mungkin ingin mencoba keduanya. Jika rata-rata setara, ambil implementasinya dengan fab.
Jika Anda memiliki beberapa info yang mana dari kedua float lebih cenderung lebih besar daripada yang lain, Anda dapat bermain di urutan perbandingan untuk mengambil keuntungan lebih baik dari evaluasi malas.
Akhirnya, Anda mungkin mendapatkan hasil yang lebih baik dengan menguraikan fungsi ini. Tidak mungkin untuk meningkatkan banyak ...
Sunting: OJ, terima kasih telah memperbaiki kode Anda. Saya menghapus komentar saya sesuai
sumber
Ini bagus jika:
Tetapi sebaliknya itu akan membawa Anda ke dalam masalah. Angka presisi ganda memiliki resolusi sekitar 16 desimal. Jika dua angka yang Anda bandingkan besarnya lebih besar daripada EPSILON * 1.0E16, maka Anda mungkin juga mengatakan:
Saya akan memeriksa pendekatan yang berbeda yang menganggap Anda perlu khawatir tentang masalah pertama dan menganggap yang kedua baik-baik saja aplikasi Anda. Sebuah solusi akan menjadi seperti:
Ini mahal secara komputasi, tetapi kadang-kadang itulah yang disebut. Inilah yang harus kita lakukan di perusahaan saya karena kita berurusan dengan perpustakaan teknik dan input dapat bervariasi dengan beberapa lusin pesanan besar.
Pokoknya, intinya adalah ini (dan berlaku untuk hampir semua masalah pemrograman): Mengevaluasi apa kebutuhan Anda, kemudian datang dengan solusi untuk memenuhi kebutuhan Anda - jangan menganggap jawaban mudah akan memenuhi kebutuhan Anda. Jika setelah evaluasi Anda menemukan itu
fabs(a-b) < EPSILON
sudah cukup, sempurna - gunakan! Namun waspadai kekurangannya dan kemungkinan solusi lainnya juga.sumber
Seperti yang telah ditunjukkan orang lain, menggunakan epsilon eksponen tetap (seperti 0,0000001) tidak akan berguna untuk nilai yang jauh dari nilai epsilon. Sebagai contoh, jika dua nilai Anda adalah 10.000.000977 dan 10000, maka ada nilai floating-point NO 32-bit antara dua angka ini - 10000 dan 10000.000977 sedekat yang Anda bisa dapatkan tanpa bit-for-bit identik. Di sini, epsilon kurang dari 0,0009 tidak ada artinya; Anda mungkin juga menggunakan operator kesetaraan lurus.
Demikian juga, ketika kedua nilai mendekati ukuran epsilon, kesalahan relatif tumbuh hingga 100%.
Dengan demikian, mencoba untuk mencampur nomor titik tetap seperti 0,00001 dengan nilai-nilai floating-point (di mana eksponen sewenang-wenang) adalah latihan sia-sia. Ini hanya akan bekerja jika Anda dapat yakin bahwa nilai operan terletak di dalam domain sempit (yaitu, dekat dengan beberapa eksponen tertentu), dan jika Anda memilih nilai epsilon dengan benar untuk tes tertentu. Jika Anda mengeluarkan angka dari udara ("Hei! 0,00001 kecil, jadi pasti bagus!"), Anda akan mengalami kesalahan numerik. Saya telah menghabiskan banyak waktu men-debug kode numerik yang buruk di mana beberapa bodoh bodoh melemparkan nilai-nilai epsilon acak untuk membuat lagi kasus uji bekerja.
Jika Anda melakukan pemrograman numerik dalam bentuk apa pun dan yakin Anda perlu meraih epsil titik tetap, BACA PASAL BRUCE PADA PERBANDINGAN NOMOR TITIK .
Membandingkan Nomor Floating Point
sumber
Qt mengimplementasikan dua fungsi, mungkin Anda bisa belajar darinya:
Dan Anda mungkin perlu fungsi-fungsi berikut, karena
sumber
Perbandingan tujuan umum angka floating-point umumnya tidak ada artinya. Cara membandingkan sangat tergantung pada masalah yang dihadapi. Dalam banyak masalah, angka cukup didiskritisasi untuk memungkinkan membandingkannya dalam toleransi yang diberikan. Sayangnya, ada banyak masalah, di mana trik seperti itu tidak berhasil. Sebagai satu contoh, pertimbangkan bekerja dengan fungsi (langkah) Heaviside dari sejumlah pertanyaan (opsi stok digital muncul di benak) ketika pengamatan Anda sangat dekat dengan penghalang. Melakukan perbandingan berbasis toleransi tidak akan banyak gunanya, karena secara efektif akan mengubah masalah dari penghalang asli ke dua yang baru. Sekali lagi, tidak ada solusi tujuan umum untuk masalah-masalah seperti itu dan solusi khusus mungkin perlu sejauh mengubah metode numerik untuk mencapai stabilitas.
sumber
Sayangnya, bahkan kode "boros" Anda salah. EPSILON adalah nilai terkecil yang dapat ditambahkan ke 1.0 dan mengubah nilainya. Nilai 1.0 sangat penting - angka yang lebih besar tidak berubah ketika ditambahkan ke EPSILON. Sekarang, Anda dapat mengatur nilai ini ke angka yang Anda bandingkan untuk mengetahui apakah mereka berbeda atau tidak. Ekspresi yang benar untuk membandingkan dua ganda adalah:
Ini minimal. Namun, secara umum, Anda ingin memperhitungkan kebisingan dalam perhitungan Anda dan mengabaikan beberapa bit yang paling tidak signifikan, sehingga perbandingan yang lebih realistis akan terlihat seperti:
Jika kinerja perbandingan sangat penting bagi Anda dan Anda tahu rentang nilai Anda, maka Anda harus menggunakan angka titik tetap.
sumber
EPSILON
dalam pertanyaannya adalahDBL_EPSILON
atauFLT_EPSILON
? Masalahnya adalah dalam imajinasi Anda sendiri, di mana Anda menggantiDBL_EPSILON
(yang memang akan menjadi pilihan yang salah) menjadi kode yang tidak menggunakannya.Kelas saya berdasarkan jawaban yang diposting sebelumnya. Sangat mirip dengan kode Google tetapi saya menggunakan bias yang mendorong semua nilai NaN di atas 0xFF000000. Itu memungkinkan pemeriksaan yang lebih cepat untuk NaN.
Kode ini dimaksudkan untuk menunjukkan konsep, bukan menjadi solusi umum. Kode Google sudah menunjukkan cara menghitung semua nilai spesifik platform dan saya tidak ingin menduplikasi semua itu. Saya telah melakukan pengujian terbatas pada kode ini.
sumber
Inilah bukti bahwa menggunakan
std::numeric_limits::epsilon()
bukanlah jawabannya - gagal untuk nilai yang lebih dari satu:Bukti komentar saya di atas:
Menjalankan menghasilkan output ini:
Perhatikan bahwa dalam kasus kedua (satu dan hanya lebih besar dari satu), dua nilai input sedekat mungkin, dan masih membandingkan sebagai tidak dekat. Dengan demikian, untuk nilai yang lebih besar dari 1,0, Anda mungkin juga menggunakan tes kesetaraan. Epsilon yang diperbaiki tidak akan menyelamatkan Anda saat membandingkan nilai floating-point.
sumber
return *(reinterpret_cast<double*>(&x));
meskipun biasanya berhasil, sebenarnya perilaku tidak terdefinisi.numeric_limits<>::epsilon
dan titik lantai IEEE 754.Menemukan implementasi lain yang menarik di: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
sumber
Saya akan sangat waspada terhadap salah satu jawaban ini yang melibatkan pengurangan floating point (mis., Fabs (ab) <epsilon). Pertama, angka floating point menjadi lebih jarang pada magnitudo lebih besar dan pada magnitudo cukup tinggi di mana jarak lebih besar dari epsilon, Anda mungkin juga melakukan a == b. Kedua, mengurangi dua angka floating point yang sangat dekat (karena ini akan cenderung, mengingat bahwa Anda sedang mencari kesetaraan dekat) adalah persis bagaimana Anda mendapatkan pembatalan bencana .
Meskipun tidak portabel, saya pikir jawaban grom melakukan pekerjaan terbaik untuk menghindari masalah ini.
sumber
a
danb
sendiri. Sama sekali tidak ada masalah dengan menggunakan pengurangan floating point sebagai bagian dari perbandingan fuzzy (meskipun seperti yang orang lain katakan, nilai epsilon absolut mungkin atau mungkin tidak sesuai untuk kasus penggunaan tertentu.)Sebenarnya ada kasus dalam perangkat lunak numerik di mana Anda ingin memeriksa apakah dua angka floating point persis sama. Saya memposting ini pada pertanyaan serupa
https://stackoverflow.com/a/10973098/1447411
Jadi Anda tidak bisa mengatakan bahwa "CompareDoubles1" salah secara umum.
sumber
Itu tergantung pada seberapa tepat perbandingan yang Anda inginkan. Jika Anda ingin membandingkan dengan angka yang persis sama, maka cukup dengan ==. (Anda hampir tidak pernah ingin melakukan ini kecuali Anda benar-benar menginginkan nomor yang sama persis.) Pada platform yang layak Anda juga dapat melakukan hal berikut:
karena
fabs
cenderung cukup cepat. Maksud saya cukup cepat pada dasarnya adalah bitwise AND, jadi lebih baik cepat.Dan trik integer untuk membandingkan ganda dan float bagus tetapi cenderung membuatnya lebih sulit untuk menangani berbagai pipa CPU secara efektif. Dan tentu saja ini tidak lebih cepat pada arsitektur pesanan tertentu karena menggunakan stack sebagai area penyimpanan sementara untuk nilai-nilai yang sering digunakan. (Load-hit-store untuk mereka yang peduli.)
sumber
Dalam hal skala jumlah:
Jika
epsilon
sebagian kecil dari besaran kuantitas (yaitu nilai relatif) dalam beberapa pengertianA
danB
jenis fisik tertentu dapat dibandingkan dalam arti yang sama, daripada yang saya pikir, bahwa yang berikut ini cukup benar:sumber
Saya menggunakan kode ini:
sumber
epsilon
.epsilon
hanyalah jarak antara 1 dan angka representatif berikutnya setelah 1. Paling-paling, kode itu hanya mencoba untuk memeriksa apakah kedua angka itu persis sama satu sama lain, tetapi karena non-kekuatan 2 sedang dikalikan denganepsilon
, itu bahkan tidak melakukan itu dengan benar.std::fabs(std::min(v1, v2))
tidak benar - untuk input negatif memilih input yang besarnya lebih besar.Saya menulis ini untuk java, tetapi mungkin Anda merasa berguna. Ia menggunakan long bukannya double, tetapi merawat NaN, subnormal, dll.
Ingatlah bahwa setelah beberapa operasi floating-point, angka bisa sangat berbeda dari yang kita harapkan. Tidak ada kode untuk memperbaikinya.
sumber
Bagaimana dengan ini?
Saya telah melihat berbagai pendekatan - tetapi tidak pernah melihat ini, jadi saya penasaran ingin mendengar komentar juga!
sumber
Saya menggunakan fungsi ini untuk proyek kecil saya dan berfungsi, tetapi perhatikan hal berikut:
Kesalahan presisi ganda dapat membuat kejutan bagi Anda. Katakanlah epsilon = 1.0e-6, maka 1.0 dan 1.000001 TIDAK boleh dianggap sama dengan kode di atas, tetapi pada mesin saya fungsinya menganggap mereka sama, ini karena 1.000001 tidak dapat secara tepat diterjemahkan ke format biner, mungkin 1,0000009xxx. Saya mengujinya dengan 1.0 dan 1.0000011 dan kali ini saya mendapatkan hasil yang diharapkan.
sumber
Ini adalah solusi lain dengan lambda:
sumber
Cara saya mungkin tidak benar tetapi bermanfaat
Konversikan kedua float menjadi string lalu lakukan perbandingan string
overlay operator juga bisa dilakukan
sumber
Anda tidak dapat membandingkan dua
double
dengan yang diperbaikiEPSILON
. Tergantung pada nilaidouble
,EPSILON
bervariasi.Perbandingan ganda yang lebih baik adalah:
sumber
Dengan cara yang lebih umum:
sumber
a
danb
sudah lebih kecil dariepsilon()
sana perbedaannya mungkin masih signifikan. Sebaliknya jika angkanya sangat besar maka bahkan beberapa bit kesalahan akan membuat perbandingan gagal bahkan jika Anda ingin angkanya dianggap sama. Jawaban ini persis jenis algoritma perbandingan "generik" yang ingin Anda hindari.Mengapa tidak melakukan XOR bitwise? Dua angka floating point sama jika bit yang sesuai sama. Saya pikir, keputusan untuk menempatkan bit eksponen sebelum mantissa dibuat untuk mempercepat perbandingan dua pelampung. Saya pikir, banyak jawaban di sini tidak memiliki titik perbandingan epsilon. Nilai Epsilon hanya tergantung pada angka presisi floating point mana yang dibandingkan. Misalnya, setelah melakukan beberapa aritmatika dengan pelampung Anda mendapatkan dua angka: 2.5642943554342 dan 2.5642943554345. Mereka tidak sama, tetapi untuk solusinya hanya 3 angka desimal sehingga mereka sama: 2.564 dan 2.564. Dalam hal ini Anda memilih epsilon sama dengan 0,001. Perbandingan Epsilon juga dimungkinkan dengan bitor XOR. Koreksi saya jika saya salah.
sumber