Pertama-tama, nilai floating point tidak "acak" dalam perilaku mereka. Perbandingan yang tepat dapat dan memang masuk akal dalam banyak penggunaan di dunia nyata. Tetapi jika Anda akan menggunakan floating point, Anda perlu mengetahui cara kerjanya. Kesalahan di sisi asumsi floating point berfungsi seperti bilangan real akan membuat Anda mendapatkan kode yang cepat rusak. Kesalahan di sisi dengan asumsi hasil floating point memiliki fuzz acak besar yang terkait dengan mereka (seperti kebanyakan jawaban di sini menyarankan) akan membuat Anda kode yang tampaknya bekerja pada awalnya tetapi akhirnya memiliki kesalahan besar-besaran dan kasus sudut rusak.
Pertama-tama, jika Anda ingin memprogram dengan floating point, Anda harus membaca ini:
Yang Harus Diketahui Setiap Ilmuwan Komputer Tentang Aritmatika Titik Apung
Ya, baca semua itu. Jika itu terlalu banyak beban, Anda harus menggunakan bilangan bulat / titik tetap untuk perhitungan Anda sampai Anda punya waktu untuk membacanya. :-)
Sekarang, dengan itu, masalah terbesar dengan perbandingan floating point yang tepat turun ke:
Fakta bahwa banyak nilai yang dapat Anda tulis di sumbernya, atau dibaca dengan scanf
atau strtod
, tidak ada sebagai nilai floating point dan dikonversi secara diam-diam ke perkiraan terdekat. Inilah yang dibicarakan jawaban iblis9733.
Fakta bahwa banyak hasil bulat karena tidak memiliki cukup presisi untuk mewakili hasil yang sebenarnya. Contoh mudah di mana Anda dapat melihat ini adalah menambahkan x = 0x1fffffe
dan y = 1
mengapung. Di sini, x
memiliki 24 bit presisi dalam mantissa (ok) dan y
hanya 1 bit, tetapi ketika Anda menambahkannya, bit mereka tidak berada di tempat yang tumpang tindih, dan hasilnya akan membutuhkan 25 bit presisi. Sebaliknya, ia dibulatkan (ke0x2000000
dalam mode pembulatan default).
Fakta bahwa banyak hasil bulat karena membutuhkan banyak tempat tanpa batas untuk nilai yang benar. Ini mencakup kedua hasil rasional seperti 1/3 (yang Anda kenal dari desimal di mana ia mengambil banyak tempat tak terhingga) tetapi juga 1/10 (yang juga mengambil banyak tempat tak terhingga dalam biner, karena 5 bukan kekuatan 2), serta hasil irasional seperti akar kuadrat dari apa pun yang bukan kuadrat sempurna.
Pembulatan ganda. Pada beberapa sistem (terutama x86), ekspresi floating point dievaluasi dengan presisi lebih tinggi daripada tipe nominalnya. Ini berarti bahwa ketika salah satu dari jenis pembulatan di atas terjadi, Anda akan mendapatkan dua langkah pembulatan, pertama pembulatan hasil ke jenis presisi yang lebih tinggi, kemudian pembulatan ke jenis akhir. Sebagai contoh, perhatikan apa yang terjadi dalam desimal jika Anda membulatkan 1,49 ke bilangan bulat (1), dibandingkan apa yang terjadi jika Anda membulatkannya ke satu tempat desimal (1,5) kemudian bulatkan hasilnya menjadi bilangan bulat (2). Ini sebenarnya adalah salah satu area yang paling menjijikkan untuk ditangani dalam floating point, karena perilaku kompiler (terutama untuk kompiler buggy, non-conforming seperti GCC) tidak dapat diprediksi.
Fungsi transendental ( trig
, exp
, log
, dll) tidak ditentukan untuk memiliki hasil benar bulat; hasilnya hanya ditentukan untuk menjadi benar dalam satu unit di tempat terakhir presisi (biasanya disebut sebagai 1ulp ).
Saat Anda menulis kode floating point, Anda perlu mengingat apa yang Anda lakukan dengan angka-angka yang dapat menyebabkan hasil menjadi tidak tepat, dan membuat perbandingan yang sesuai. Sering kali masuk akal untuk membandingkan dengan "epsilon", tetapi epsilon itu harus didasarkan pada besarnya angka yang Anda bandingkan , bukan konstanta absolut. (Dalam kasus di mana epsilon konstan konstan akan bekerja, itu sangat menunjukkan bahwa titik tetap, bukan titik mengambang, adalah alat yang tepat untuk pekerjaan itu!)
Sunting: Secara khusus, cek epsilon dengan magnitudo relatif akan terlihat seperti:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Di mana FLT_EPSILON
konstanta dari float.h
(ganti dengan DBL_EPSILON
untuk double
s atau LDBL_EPSILON
untuk long double
s) dan K
adalah konstanta yang Anda pilih sedemikian rupa sehingga akumulasi kesalahan perhitungan Anda pasti dibatasi oleh K
unit di tempat terakhir (dan jika Anda tidak yakin Anda mendapatkan kesalahan perhitungan terikat benar, buat K
beberapa kali lebih besar dari apa yang seharusnya perhitungan Anda katakan).
Akhirnya, perhatikan bahwa jika Anda menggunakan ini, beberapa perawatan khusus mungkin diperlukan mendekati nol, karena FLT_EPSILON
tidak masuk akal untuk penyangkalan. Perbaikan cepat akan membuatnya:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
dan juga gantikan DBL_MIN
jika menggunakan ganda.
fabs(x+y)
bermasalah jikax
dany
(dapat) memiliki tanda yang berbeda. Namun, jawaban yang bagus terhadap gelombang perbandingan pemujaan kargo.x
dany
memiliki tanda yang berbeda, itu tidak masalah. Sisi kanan akan "terlalu kecil", tetapi karenax
dany
memiliki tanda yang berbeda, mereka tidak boleh membandingkan sama saja. (Kecuali jika mereka sangat kecil sehingga tidak normal, tetapi kemudian kasus kedua menangkapnya)Karena 0 benar-benar direpresentasikan sebagai angka floating-point IEEE754 (atau menggunakan implementasi lain dari nomor fp yang pernah saya kerjakan) perbandingan dengan 0 mungkin aman. Namun, Anda mungkin tergigit jika program Anda menghitung nilai (seperti
theView.frame.origin.x
) yang Anda yakini bernilai 0 tetapi perhitungan Anda tidak dapat menjamin 0.Untuk sedikit memperjelas, perhitungan seperti:
akan (kecuali bahasa atau sistem Anda rusak) membuat nilai sehingga (areal == 0,0) mengembalikan true tetapi perhitungan lain seperti
mungkin tidak.
Jika Anda dapat meyakinkan diri sendiri bahwa perhitungan Anda menghasilkan nilai yang 0 (dan bukan hanya bahwa mereka menghasilkan nilai yang seharusnya 0) maka Anda dapat melanjutkan dan membandingkan nilai fp dengan 0. Jika Anda tidak dapat meyakinkan diri Anda sendiri ke tingkat yang diperlukan , berpegang teguh pada pendekatan biasa 'kesetaraan yang ditoleransi'.
Dalam kasus terburuk, perbandingan nilai-nilai fp yang ceroboh bisa sangat berbahaya: pikirkan avionik, panduan senjata, operasi pembangkit listrik, navigasi kendaraan, hampir semua aplikasi yang perhitungannya memenuhi dunia nyata.
Bagi Angry Birds, tidak begitu berbahaya.
sumber
1.30 - 2*(0.65)
adalah contoh sempurna dari ekspresi yang jelas mengevaluasi ke 0,0 jika kompiler Anda mengimplementasikan IEEE 754, karena ganda direpresentasikan sebagai0.65
dan1.30
memiliki signifikansi yang sama, dan perkalian dengan dua jelas tepat.Saya ingin memberikan jawaban yang sedikit berbeda dari yang lain. Mereka bagus untuk menjawab pertanyaan Anda sebagaimana dinyatakan tetapi mungkin tidak untuk apa yang perlu Anda ketahui atau apa masalah sebenarnya Anda.
Titik apung dalam grafik baik-baik saja! Tetapi hampir tidak perlu membandingkan pelampung secara langsung. Mengapa Anda perlu melakukan itu? Grafik menggunakan float untuk menentukan interval. Dan membandingkan jika float berada dalam interval juga didefinisikan oleh float selalu didefinisikan dengan baik dan hanya perlu konsisten, tidak akurat atau tepat! Selama piksel (yang juga merupakan interval!) Dapat ditetapkan itu semua kebutuhan grafis.
Jadi jika Anda ingin menguji apakah poin Anda di luar rentang [0..width [ini baik-baik saja. Pastikan Anda mendefinisikan inklusi secara konsisten. Misalnya selalu mendefinisikan di dalam adalah (x> = 0 && x <lebar). Hal yang sama berlaku untuk tes persimpangan atau tekan.
Namun, jika Anda menyalahgunakan koordinat grafik sebagai semacam bendera, seperti misalnya untuk melihat apakah jendela merapat atau tidak, Anda tidak boleh melakukan ini. Gunakan bendera boolean yang terpisah dari lapisan presentasi grafik.
sumber
Membandingkan dengan nol dapat menjadi operasi yang aman, selama nol bukan nilai yang dihitung (seperti yang disebutkan dalam jawaban di atas). Alasan untuk ini adalah bahwa nol adalah angka yang dapat direpresentasikan secara sempurna dalam floating point.
Berbicara nilai-nilai yang dapat direpresentasikan dengan sempurna, Anda mendapatkan rentang 24 bit dalam konsep power-of-two (presisi tunggal). Jadi 1, 2, 4 dapat direpresentasikan dengan sempurna, seperti juga 0,5, 0,25, dan 0,125. Selama semua bit penting Anda dalam 24-bit, Anda adalah emas. Jadi 10.625 dapat diwakili secara tepat.
Ini bagus, tetapi akan cepat hancur di bawah tekanan. Dua skenario muncul dalam pikiran: 1) Ketika suatu perhitungan dilibatkan. Jangan percaya bahwa sqrt (3) * sqrt (3) == 3. Tidak akan seperti itu. Dan itu mungkin tidak akan berada dalam epsilon, seperti yang disarankan oleh beberapa jawaban lainnya. 2) Ketika ada non-power-of-2 (NPOT) yang terlibat. Jadi mungkin terdengar aneh, tetapi 0,1 adalah deret tak terbatas dalam biner dan karenanya perhitungan apa pun yang melibatkan angka seperti ini akan tidak tepat sejak awal.
(Oh dan pertanyaan awal menyebutkan perbandingan dengan nol. Jangan lupa bahwa -0.0 juga merupakan nilai floating-point yang valid.)
sumber
['Jawaban yang benar' lebih menarik daripada memilih
K
. MemilihK
akhirnya sama ad-hoc seperti memilihVISIBLE_SHIFT
tetapi memilihK
kurang jelas karena tidak sepertiVISIBLE_SHIFT
itu tidak didasarkan pada properti tampilan. Jadi pilih racun Anda - pilihK
atau pilihVISIBLE_SHIFT
. Jawaban ini menganjurkan pemilihanVISIBLE_SHIFT
dan kemudian menunjukkan kesulitan dalam memilihK
]Justru karena kesalahan bulat, Anda tidak boleh menggunakan perbandingan nilai 'tepat' untuk operasi logis. Dalam kasus spesifik Anda tentang posisi pada tampilan visual, tidak mungkin apakah posisi tersebut 0,0 atau 0,0000000003 - perbedaannya tidak terlihat oleh mata. Jadi logika Anda harus seperti:
Namun, pada akhirnya, 'tidak terlihat oleh mata' akan tergantung pada properti tampilan Anda. Jika Anda dapat membatasi tampilan (Anda seharusnya bisa); lalu pilih
VISIBLE_SHIFT
menjadi bagian dari batas atas itu.Sekarang, 'jawaban yang benar' terletak di atas,
K
jadi mari kita jelajahi memilihK
. 'Jawaban yang benar' di atas mengatakan:Jadi kita butuh
K
. Jika mendapatkanK
lebih sulit, kurang intuitif daripada memilih sayaVISIBLE_SHIFT
maka Anda akan memutuskan apa yang cocok untuk Anda. Untuk menemukanK
kita akan menulis sebuah program pengujian yang terlihat pada sekelompokK
nilai sehingga kita dapat melihat bagaimana perilakunya. Seharusnya jelas bagaimana memilihK
, jika 'jawaban yang tepat' dapat digunakan. Tidak?Kita akan menggunakan, sebagai rincian 'jawaban yang benar':
Mari kita coba semua nilai K:
Ah, jadi K harus 1e16 atau lebih besar jika saya ingin 1e-13 menjadi 'nol'.
Jadi, saya katakan Anda memiliki dua opsi:
K
.sumber
K
yang sulit dan tidak intuitif untuk dipilih.Pertanyaan yang benar: bagaimana cara membandingkan poin di Cocoa Touch?
Jawaban yang benar: CGPointEqualToPoint ().
Pertanyaan yang berbeda: Apakah dua nilai yang dihitung sama?
Jawabannya diposting di sini: Tidak.
Bagaimana cara mengecek apakah mereka sudah dekat? Jika Anda ingin memeriksa apakah mereka dekat, maka jangan gunakan CGPointEqualToPoint (). Tapi, jangan periksa untuk melihat apakah mereka sudah dekat. Lakukan sesuatu yang masuk akal di dunia nyata, seperti memeriksa untuk melihat apakah suatu titik berada di luar garis atau jika suatu titik berada di dalam bola.
sumber
Terakhir kali saya memeriksa standar C, tidak ada persyaratan untuk operasi floating point pada ganda (total 64 bit, 53 bit mantissa) agar akurat hingga lebih dari presisi itu. Namun, beberapa perangkat keras mungkin melakukan operasi dalam register dengan presisi yang lebih besar, dan persyaratan itu ditafsirkan berarti tidak ada persyaratan untuk menghapus bit urutan yang lebih rendah (di luar presisi dari angka yang dimuat ke dalam register). Jadi Anda bisa mendapatkan hasil perbandingan yang tidak terduga seperti ini tergantung pada apa yang tersisa di register dari siapa pun yang tidur di sana.
Yang mengatakan, dan meskipun upaya saya untuk menghapusnya setiap kali saya melihatnya, pakaian tempat saya bekerja memiliki banyak kode C yang dikompilasi menggunakan gcc dan berjalan di linux, dan kami belum melihat hasil yang tak terduga ini dalam waktu yang sangat lama . Saya tidak tahu apakah ini karena gcc sedang membersihkan bit orde rendah untuk kita, register 80-bit tidak digunakan untuk operasi ini pada komputer modern, standar telah diubah, atau apa. Saya ingin tahu apakah ada yang bisa mengutip pasal dan ayat.
sumber
Anda dapat menggunakan kode tersebut untuk membandingkan float dengan nol:
Ini akan dibandingkan dengan akurasi 0,1, itu cukup untuk CGFloat dalam kasus ini.
sumber
int
tanpa mengasuransikantheView.frame.origin.x
berada dalam / dekat kisaranint
mengarah ke perilaku tidak terdefinisi (UB) - atau dalam hal ini, 1/100 kisaranint
.}
sumber
Saya menggunakan fungsi perbandingan berikut untuk membandingkan sejumlah tempat desimal:
sumber
Saya akan mengatakan hal yang benar adalah dengan menyatakan setiap angka sebagai objek, dan kemudian mendefinisikan tiga hal dalam objek itu: 1) operator kesetaraan. 2) metode setAcceptableDifference. 3) nilai itu sendiri. Operator persamaan mengembalikan nilai true jika perbedaan absolut dari dua nilai kurang dari nilai yang ditetapkan dapat diterima.
Anda dapat subklas objek agar sesuai dengan masalah. Misalnya, batang bundar logam antara 1 dan 2 inci mungkin dianggap berdiameter sama jika diameternya berbeda kurang dari 0,0001 inci. Jadi, Anda akan memanggil setAcceptableDifference dengan parameter 0,0001, dan kemudian menggunakan operator kesetaraan dengan percaya diri.
sumber