Misalnya, saya ingin menampilkan daftar tombol mulai 0,0,5, ... 5, yang melompat untuk setiap 0,5. Saya menggunakan loop for untuk melakukan itu, dan memiliki warna berbeda di tombol STANDARD_LINE:
var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;
for(var i=0;i<=MAX;i=i+DIFF){
button.text=i+'';
if(i==STANDARD_LINE){
button.color='red';
}
}
Pada kasus ini seharusnya tidak ada kesalahan pembulatan karena setiap nilai tepat di IEEE 754.Tapi saya berjuang jika saya harus mengubahnya untuk menghindari perbandingan kesetaraan floating point:
var MAX=10;
var STANDARD_LINE=3;
for(var i=0;i<=MAX;i++){
button.text=i/2.0+'';
if(i==STANDARD_LINE/2.0){
button.color='red';
}
}
Di satu sisi, kode asli lebih sederhana dan maju ke saya. Tetapi ada satu hal yang saya pertimbangkan: apakah saya == STANDARD_LINE menyesatkan rekan satu tim junior? Apakah itu menyembunyikan fakta bahwa angka floating point mungkin memiliki kesalahan pembulatan? Setelah membaca komentar dari posting ini:
sepertinya ada banyak pengembang yang tidak tahu beberapa angka float tepat. Haruskah saya menghindari perbandingan kesetaraan angka float meskipun valid dalam kasus saya? Atau apakah saya terlalu memikirkan hal ini?
i
hanya akan menjadi angka utuh dalam daftar kedua. Coba hapus yang kedua/2.0
.button
tidak berubah di mana pun di lingkaran Anda. Bagaimana daftar tombol diakses? Melalui indeks ke array atau mekanisme lain? Jika itu dengan indeks akses ke array, ini adalah argumen lain yang mendukung beralih ke bilangan bulat.Jawaban:
Saya akan selalu menghindari operasi floating-point berturut-turut kecuali model yang saya hitung membutuhkannya. Aritmatika floating-point tidak intuitif untuk sebagian besar dan merupakan sumber kesalahan utama. Dan menceritakan kasus-kasus yang menyebabkan kesalahan dari mereka yang tidak melakukannya adalah perbedaan yang bahkan lebih halus!
Oleh karena itu, menggunakan pelampung sebagai penghitung lingkaran adalah cacat yang menunggu untuk terjadi dan paling tidak memerlukan komentar latar belakang yang gemuk menjelaskan mengapa boleh saja menggunakan 0,5 di sini, dan bahwa ini tergantung pada nilai numerik tertentu. Pada titik itu, menulis ulang kode untuk menghindari counter float mungkin akan menjadi opsi yang lebih mudah dibaca. Dan keterbacaan berada di sebelah kebenaran dalam hierarki persyaratan profesional.
sumber
DIFF must be an exactly-representable double that evenly divides STANDARD_LINE
. Jika Anda tidak ingin menulis komentar itu (dan mengandalkan semua pengembang masa depan untuk mengetahui cukup tentang floating point IEEE754 binary64 untuk memahaminya), maka jangan menulis kode dengan cara ini. yaitu jangan menulis kode dengan cara ini. Terutama karena itu mungkin bahkan tidak lebih efisien: Penambahan FP memiliki latensi yang lebih tinggi daripada penambahan bilangan bulat, dan itu adalah ketergantungan loop-carry. Juga, kompiler (bahkan kompiler JIT?) Mungkin lebih baik dalam membuat loop dengan counter integer.Sebagai aturan umum, loop harus ditulis sedemikian rupa untuk berpikir melakukan sesuatu n kali. Jika Anda menggunakan indeks floating point, itu bukan lagi pertanyaan melakukan sesuatu n kali melainkan menjalankan sampai kondisi terpenuhi. Jika kondisi ini sangat mirip dengan
i<n
yang diharapkan oleh banyak programmer, maka kode tersebut tampaknya melakukan satu hal ketika sebenarnya melakukan hal lain yang dapat dengan mudah disalahartikan oleh programmer yang membaca kode.Ini agak subyektif, tetapi menurut pendapat saya, jika Anda dapat menulis ulang loop untuk menggunakan indeks integer untuk mengulang beberapa kali, Anda harus melakukannya. Jadi pertimbangkan alternatif berikut:
Loop bekerja dalam hal bilangan bulat. Dalam hal ini
i
adalah bilangan bulat danSTANDARD_LINE
dipaksa ke bilangan bulat juga. Ini tentu saja akan mengubah posisi garis standar Anda jika kebetulan ada pembulatan dan juga untukMAX
, jadi Anda harus tetap berusaha untuk mencegah pembulatan untuk rendering yang akurat. Namun Anda masih memiliki keuntungan mengubah parameter dalam hal piksel dan bukan bilangan bulat tanpa harus khawatir tentang perbandingan poin mengambang.sumber
i
danSTANDARD_LINE
hanya terlihat seperti bilangan bulat. Tidak ada paksaan sama sekali, danDIFF
,MAX
danSTANDARD_LINE
semuanya hanyaNumber
s.Number
s digunakan sebagai bilangan bulat harus aman di bawah ini2**53
, mereka masih angka floating point.Saya setuju dengan semua jawaban lain yang menggunakan variabel loop non-integer umumnya gaya buruk bahkan dalam kasus seperti ini di mana ia akan bekerja dengan benar. Tapi menurut saya ada alasan lain mengapa gaya ini buruk.
Kode Anda "tahu" bahwa lebar garis yang tersedia adalah kelipatan 0,5 dari 0 hingga 5,0. Haruskah itu Sepertinya itu adalah keputusan antarmuka pengguna yang mungkin dengan mudah berubah (misalnya, mungkin Anda ingin kesenjangan antara lebar yang tersedia menjadi lebih besar seperti yang dilakukan lebar. 0,25, 0,5, 0,75, 1.0, 1.5, 2.0, 2.5, 3.0, 3.0, 4.0, 5.0 atau sesuatu).
Kode Anda "tahu" bahwa lebar garis yang tersedia semuanya memiliki representasi "bagus" baik sebagai angka floating-point dan sebagai desimal. Itu juga sepertinya sesuatu yang mungkin berubah. (Anda mungkin ingin 0,1, 0,2, 0,3, ... di beberapa titik.)
Kode Anda "tahu" bahwa teks yang akan dimasukkan pada tombol hanyalah Javascript yang mengubah nilai-nilai titik-mengambang itu. Itu juga sepertinya sesuatu yang mungkin berubah. (Misalnya, mungkin suatu hari Anda akan menginginkan lebar seperti 1/3, yang Anda mungkin tidak ingin ditampilkan sebagai 0,33333333333333 atau apa pun. Atau mungkin Anda ingin melihat "1.0" alih-alih "1" untuk konsistensi dengan "1,5" .)
Semua ini bagi saya terasa seperti manifestasi dari kelemahan tunggal, yang merupakan semacam campuran lapisan. Angka-angka floating-point adalah bagian dari logika internal perangkat lunak. Teks yang ditampilkan pada tombol adalah bagian dari antarmuka pengguna. Mereka harus lebih terpisah daripada yang ada dalam kode di sini. Pengertian seperti "mana di antara ini yang merupakan standar yang harus disorot?" adalah masalah antarmuka pengguna, dan mereka mungkin tidak boleh terikat dengan nilai-nilai floating-point tersebut. Dan loop Anda di sini benar-benar (atau setidaknya seharusnya) loop atas tombol , bukan atas lebar garis . Ditulis seperti itu, godaan untuk menggunakan variabel loop mengambil nilai-nilai non-integer menghilang: Anda hanya akan menggunakan integer yang berurutan atau for ... in / for ... of loop.
Perasaan saya adalah bahwa sebagian besar kasus di mana seseorang mungkin tergoda untuk mengulangi angka-angka non-integer adalah seperti ini: ada alasan lain, sama sekali tidak terkait dengan masalah numerik, mengapa kode harus diatur secara berbeda. (Tidak semua kasus; Saya dapat membayangkan beberapa algoritma matematika mungkin paling jelas dinyatakan dalam bentuk loop atas nilai-nilai non-integer.)
sumber
Satu kode bau menggunakan floats in loop seperti itu.
Looping dapat dilakukan dalam banyak cara, tetapi dalam 99,9% dari kasus Anda harus tetap pada kenaikan 1 atau pasti akan ada kebingungan, tidak hanya oleh pengembang junior.
sumber
Ya, Anda ingin menghindari ini.
Angka floating point adalah salah satu jebakan terbesar bagi programmer yang tidak menaruh curiga (yang berarti, dalam pengalaman saya, hampir semua orang). Dari tergantung pada tes kesetaraan floating point, untuk mewakili uang sebagai floating point, itu semua adalah masalah besar. Menambahkan satu pelampung di atas yang lain adalah salah satu pelanggar terbesar. Ada banyak volume literatur ilmiah tentang hal-hal seperti ini.
Gunakan angka floating point tepat di tempat-tempat yang sesuai, misalnya ketika melakukan perhitungan matematika aktual di mana Anda membutuhkannya (seperti trigonometri, merencanakan fungsi grafik dll.) Dan berhati-hatilah saat melakukan operasi serial. Kesetaraan benar. Pengetahuan tentang set angka tertentu mana yang tepat menurut standar IEEE sangat misterius dan saya tidak akan pernah bergantung padanya.
Dalam kasus Anda, ada akan , dengan Undang-Undang Murphys, datang titik di mana manajemen ingin Anda tidak memiliki 0,0, 0,5, 1,0 ... tapi 0,0, 0,4, 0,8 ... atau apa pun; Anda Anda akan segera borked, dan programmer junior Anda (atau diri Anda sendiri) akan men-debug panjang dan keras sampai Anda menemukan masalah.
Dalam kode khusus Anda, saya memang akan memiliki variabel loop integer. Ini mewakili
i
tombol th, bukan angka yang sedang berjalan.Dan saya mungkin, demi kejelasan ekstra, tidak menulis
i/2
tetapii*0.5
yang membuatnya sangat jelas apa yang sedang terjadi.Catatan: seperti yang ditunjukkan dalam komentar, JavaScript sebenarnya tidak memiliki tipe terpisah untuk bilangan bulat. Tetapi bilangan bulat hingga 15 digit dijamin akurat / aman (lihat https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), maka untuk argumen seperti ini ("apakah itu lebih membingungkan / rawan kesalahan untuk bekerja dengan bilangan bulat atau non-bilangan bulat ") ini hampir tepat untuk memiliki tipe terpisah" dalam semangat "; dalam penggunaan sehari-hari (loop, koordinat layar, indeks array, dll.) tidak akan ada kejutan dengan angka integer diwakili
Number
sebagai JavaScript.sumber
Saya kira saran Anda tidak bagus. Sebagai gantinya, saya akan memperkenalkan variabel untuk jumlah tombol berdasarkan pada nilai maksimum dan spasi. Kemudian, cukup sederhana untuk mengulang indeks tombol itu sendiri.
Mungkin lebih banyak kode, tetapi juga lebih mudah dibaca dan lebih kuat.
sumber
Anda dapat menghindari semuanya dengan menghitung nilai yang ditampilkan daripada menggunakan penghitung lingkaran sebagai nilai:
sumber
Aritmatika titik apung lambat dan aritmatika bilangan bulat cepat, jadi ketika saya menggunakan titik apung, saya tidak akan menggunakannya secara tidak perlu di mana bilangan bulat dapat digunakan. Sangat berguna untuk selalu memikirkan angka floating point, bahkan konstanta, sebagai perkiraan, dengan beberapa kesalahan kecil. Ini sangat berguna selama debugging untuk mengganti angka floating point asli dengan objek floating point plus / minus di mana Anda memperlakukan setiap angka sebagai rentang, bukan titik. Dengan cara itu Anda mengungkap ketidakakuratan tumbuh progresif setelah setiap operasi aritmatika. Jadi "1,5" harus dianggap sebagai "beberapa angka antara 1,45 dan 1,55", dan "1,50" harus dianggap sebagai "beberapa angka antara 1,495 dan 1,505".
sumber