Sudah diketahui bahwa membandingkan floats untuk persamaan adalah sedikit rumit karena masalah pembulatan dan ketepatan.
Misalnya: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Apa cara yang disarankan untuk menangani hal ini dengan Python?
Tentunya ada fungsi perpustakaan standar untuk ini di suatu tempat?
python
floating-point
Gordon Wrigley
sumber
sumber
all
,any
,max
,min
masing-masing pada dasarnya satu-liners, dan mereka tidak hanya disediakan di perpustakaan, mereka builtin fungsi. Jadi alasan BDFL bukan itu. Satu baris kode yang ditulis kebanyakan orang cukup tidak canggih dan seringkali tidak berfungsi, yang merupakan alasan kuat untuk memberikan sesuatu yang lebih baik. Tentu saja setiap modul yang menyediakan strategi lain juga harus memberikan peringatan yang menjelaskan kapan mereka sesuai, dan yang lebih penting ketika mereka tidak. Analisis numerik itu sulit, itu tidak memalukan bahwa perancang bahasa biasanya tidak mencoba alat untuk membantunya.Jawaban:
Python 3.5 menambahkan
math.isclose
dancmath.isclose
fungsinya seperti dijelaskan dalam PEP 485 .Jika Anda menggunakan versi Python sebelumnya, fungsi yang setara diberikan dalam dokumentasi .
rel_tol
adalah toleransi relatif, itu dikalikan dengan semakin besar besarnya dua argumen; karena nilainya semakin besar, begitu pula perbedaan yang diizinkan di antara mereka sambil tetap menganggapnya sama.abs_tol
adalah toleransi mutlak yang diterapkan apa adanya dalam semua kasus. Jika perbedaannya kurang dari toleransi tersebut, nilainya dianggap sama.sumber
a
ataub
anumpy
array
,numpy.isclose
berfungsi.rel_tol
adalah toleransi relatif , ini dikalikan dengan semakin besar dari dua argumen; karena nilainya semakin besar, begitu pula perbedaan yang diizinkan di antara mereka sambil tetap menganggapnya sama.abs_tol
adalah toleransi mutlak yang diterapkan apa adanya dalam semua kasus. Jika perbedaannya kurang dari toleransi tersebut, nilainya dianggap sama.isclose
fungsi ini (di atas) bukan implementasi yang lengkap .isclose
selalu berpegang pada kriteria yang kurang konservatif. Saya hanya menyebutkannya karena perilaku itu berlawanan dengan saya. Jika saya menentukan dua kriteria, saya selalu mengharapkan toleransi yang lebih kecil untuk menggantikan yang lebih besar.Apakah sesederhana yang berikut ini tidak cukup baik?
sumber
abs(f1-f2) < tol*max(abs(f1),abs(f2))
. Toleransi relatif semacam ini adalah satu-satunya cara yang bermakna untuk membandingkan pelampung secara umum, karena biasanya dipengaruhi oleh kesalahan pembulatan di tempat desimal kecil.>>> abs(0.04 - 0.03) <= 0.01
:, ia menghasilkanFalse
. Saya menggunakanPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
abs(f1 - f2) <= allowed_error
tidak berfungsi seperti yang diharapkan.Saya setuju bahwa jawaban Gareth mungkin paling tepat sebagai fungsi / solusi ringan.
Tapi saya pikir akan sangat membantu untuk dicatat bahwa jika Anda menggunakan NumPy atau sedang mempertimbangkannya, ada fungsi yang dikemas untuk ini.
Namun sedikit penafian: menginstal NumPy bisa menjadi pengalaman yang tidak sepele tergantung pada platform Anda.
sumber
pip
Windows.Gunakan
decimal
modul Python , yang menyediakanDecimal
kelas.Dari komentar:
sumber
Saya tidak mengetahui apa pun di pustaka standar Python (atau di tempat lain) yang mengimplementasikan
AlmostEqual2sComplement
fungsi Dawson . Jika itu jenis perilaku yang Anda inginkan, Anda harus menerapkannya sendiri. (Dalam hal ini, daripada menggunakan retas bitwise Dawson yang cerdik, Anda mungkin akan lebih baik menggunakan tes yang lebih konvensional dari bentukif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
atau sejenisnya. Untuk mendapatkan perilaku seperti Dawson, Anda mungkin mengatakan sesuatu sepertiif abs(a-b) <= eps*max(EPS,abs(a),abs(b))
untuk beberapa perbaikan kecilEPS
; ini bukan tepatnya sama seperti Dawson, tetapi memiliki semangat yang serupa.sumber
eps1
daneps2
tentukan kerabat dan toleransi absolut: Anda siap membiarkana
danb
berbeda sekitareps1
berapa kali mereka ditambaheps2
.eps
adalah toleransi tunggal; Anda siap untuk mengizinkana
danb
berbeda sekitareps
berapa kali mereka, dengan ketentuan bahwa ukuran apa punEPS
atau lebih kecil dianggap berukuranEPS
. Jika Anda menganggapnyaEPS
sebagai nilai non-abnormal terkecil dari tipe floating-point Anda, ini sangat mirip dengan komparator Dawson (kecuali untuk faktor 2 ^ # bit karena Dawson mengukur toleransi dalam ulp).Kebijaksanaan umum bahwa angka floating-point tidak dapat dibandingkan untuk kesetaraan adalah tidak akurat. Bilangan floating-point tidak berbeda dari bilangan bulat: Jika Anda mengevaluasi "a == b", Anda akan menjadi kenyataan jika bilangan itu identik dan salah (dengan pengertian bahwa dua NaN tentu saja bukan bilangan identik).
Masalah sebenarnya adalah ini: Jika saya telah melakukan perhitungan dan tidak yakin dua angka yang harus saya bandingkan benar, lalu apa? Masalah ini sama untuk floating-point seperti halnya untuk bilangan bulat. Jika Anda mengevaluasi ekspresi integer "7/3 * 3", itu tidak akan sama dengan "7 * 3/3".
Jadi misalkan kita bertanya, "Bagaimana cara membandingkan bilangan bulat untuk kesetaraan?" dalam situasi seperti itu. Tidak ada jawaban tunggal; apa yang harus Anda lakukan tergantung pada situasi spesifik, terutama kesalahan apa yang Anda miliki dan apa yang ingin Anda capai.
Berikut beberapa pilihan yang mungkin.
Jika Anda ingin mendapatkan hasil "benar" jika angka yang tepat secara matematis akan sama, maka Anda dapat mencoba menggunakan properti dari perhitungan yang Anda lakukan untuk membuktikan bahwa Anda mendapatkan kesalahan yang sama dalam dua angka. Jika itu layak, dan Anda membandingkan dua angka yang dihasilkan dari ekspresi yang akan memberikan angka yang sama jika dihitung secara tepat, maka Anda akan mendapatkan "benar" dari perbandingan. Pendekatan lain adalah bahwa Anda dapat menganalisis properti perhitungan dan membuktikan bahwa kesalahan tidak pernah melebihi jumlah tertentu, mungkin jumlah absolut atau jumlah relatif terhadap salah satu input atau salah satu output. Dalam hal ini, Anda dapat bertanya apakah dua angka yang dihitung berbeda paling banyak jumlah itu, dan mengembalikan "benar" jika mereka berada dalam interval. Jika Anda tidak dapat membuktikan kesalahan terikat, Anda mungkin menebak dan berharap untuk yang terbaik. Salah satu cara menebak adalah dengan mengevaluasi banyak sampel acak dan melihat jenis distribusi yang Anda dapatkan dalam hasil.
Tentu saja, karena kami hanya menetapkan persyaratan bahwa Anda mendapatkan "benar" jika hasil yang tepat secara matematis sama, kami membiarkan kemungkinan bahwa Anda mendapatkan "benar" walaupun tidak sama. (Faktanya, kita dapat memenuhi persyaratan dengan selalu mengembalikan "benar". Ini membuat perhitungannya sederhana tetapi umumnya tidak diinginkan, jadi saya akan membahas memperbaiki situasi di bawah ini.)
Jika Anda ingin mendapatkan hasil "false" jika angka yang tepat secara matematis tidak sama, Anda perlu membuktikan bahwa evaluasi Anda terhadap angka menghasilkan angka yang berbeda jika angka yang tepat secara matematis tidak sama. Ini mungkin mustahil untuk tujuan praktis dalam banyak situasi umum. Jadi mari kita pertimbangkan alternatifnya.
Persyaratan yang berguna mungkin bahwa kita mendapatkan hasil "salah" jika angka yang tepat secara matematis berbeda lebih dari jumlah tertentu. Sebagai contoh, mungkin kita akan menghitung di mana bola dilemparkan dalam permainan komputer bepergian, dan kami ingin tahu apakah itu memukul kelelawar. Dalam hal ini, kami tentu ingin mendapatkan "benar" jika bola memukul kelelawar, dan kami ingin mendapatkan "salah" jika bola jauh dari kelelawar, dan kami dapat menerima jawaban "benar" yang salah jika bola masuk simulasi matematis yang tepat melewatkan kelelawar tetapi dalam milimeter memukul kelelawar. Dalam hal itu, kita perlu membuktikan (atau menebak / memperkirakan) bahwa perhitungan kita tentang posisi bola dan posisi kelelawar memiliki kesalahan gabungan paling banyak satu milimeter (untuk semua posisi yang menarik). Ini akan memungkinkan kita untuk selalu kembali "
Jadi, bagaimana Anda memutuskan apa yang akan dikembalikan ketika membandingkan angka floating-point sangat tergantung pada situasi spesifik Anda.
Mengenai cara Anda membuktikan batas kesalahan untuk perhitungan, itu bisa menjadi subjek yang rumit. Setiap implementasi floating-point menggunakan standar IEEE 754 dalam mode round-to-terdekat mengembalikan angka floating-point terdekat dengan hasil yang tepat untuk setiap operasi dasar (terutama perkalian, pembagian, penambahan, pengurangan, akar kuadrat). (Dalam kasus seri, bulat sehingga bit rendahnya genap.) (Berhati-hatilah tentang akar kuadrat dan pembagian; implementasi bahasa Anda mungkin menggunakan metode yang tidak sesuai dengan IEEE 754 untuk itu.) Karena persyaratan ini, kita tahu kesalahan dalam hasil tunggal paling banyak 1/2 dari nilai bit paling signifikan. (Jika lebih, pembulatan akan pergi ke nomor yang berbeda yaitu dalam 1/2 nilai.)
Pergi dari sana menjadi jauh lebih rumit; langkah selanjutnya adalah melakukan operasi di mana salah satu input sudah memiliki beberapa kesalahan. Untuk ekspresi sederhana, kesalahan ini dapat diikuti melalui perhitungan untuk mencapai batas kesalahan akhir. Dalam praktiknya, ini hanya dilakukan dalam beberapa situasi, seperti mengerjakan perpustakaan matematika berkualitas tinggi. Dan, tentu saja, Anda perlu kontrol yang tepat atas operasi yang dilakukan. Bahasa tingkat tinggi sering memberi kompiler banyak kelonggaran, jadi Anda mungkin tidak tahu di mana operasi urutan dilakukan.
Ada banyak lagi yang bisa (dan) ditulis tentang topik ini, tetapi saya harus berhenti di situ. Singkatnya, jawabannya adalah: Tidak ada rutinitas perpustakaan untuk perbandingan ini karena tidak ada solusi tunggal yang sesuai dengan sebagian besar kebutuhan yang layak dimasukkan ke dalam rutinitas perpustakaan. (Jika membandingkan dengan interval kesalahan relatif atau absolut sudah mencukupi untuk Anda, Anda dapat melakukannya hanya tanpa rutin perpustakaan.)
sumber
(7/3*3 == 7*3/3)
. Itu dicetakFalse
.from __future__ import division
. Jika Anda tidak melakukan itu, tidak ada angka floating point dan perbandingannya adalah antara dua bilangan bulat.Jika Anda ingin menggunakannya dalam konteks pengujian / TDD, saya akan mengatakan ini adalah cara standar:
sumber
math.isclose () telah ditambahkan ke Python 3.5 untuk itu ( kode sumber ). Ini adalah port-nya untuk Python 2. Perbedaannya dari One-liner dari Mark Ransom adalah ia dapat menangani "inf" dan "-inf" dengan benar.
sumber
Saya menemukan perbandingan berikut bermanfaat:
sumber
str(.1 + .2) == str(.3)
mengembalikan False. Metode yang dijelaskan di atas hanya berfungsi untuk Python 2.Untuk beberapa kasus di mana Anda dapat memengaruhi representasi nomor sumber, Anda bisa merepresentasikannya sebagai fraksi alih-alih mengapung, menggunakan pembilang bilangan bulat dan penyebut. Dengan begitu Anda dapat memiliki perbandingan yang tepat.
Lihat Fraksi dari fraksi modul untuk rincian.
sumber
Saya menyukai saran @Sesquipedal tetapi dengan modifikasi (kasus penggunaan khusus ketika kedua nilai 0 mengembalikan False). Dalam kasus saya, saya menggunakan Python 2.7 dan hanya menggunakan fungsi sederhana:
sumber
Berguna untuk kasus di mana Anda ingin memastikan 2 angka sama 'hingga presisi', tidak perlu menentukan toleransi:
Temukan presisi minimum dari 2 angka
Bulat keduanya hingga presisi minimum dan bandingkan
Seperti yang ditulis, hanya berfungsi untuk angka tanpa 'e' dalam representasi string mereka (artinya 0,9999999999995e-4 <angka <= 0,9999999999995e11)
Contoh:
sumber
isclose(1.0, 1.1)
menghasilkanFalse
, danisclose(0.1, 0.000000000001)
mengembalikanTrue
.Untuk membandingkan hingga desimal yang diberikan tanpa
atol/rtol
:sumber
Ini mungkin hack yang agak jelek, tetapi ini bekerja cukup baik ketika Anda tidak membutuhkan lebih dari presisi float default (sekitar 11 desimal).
Fungsi round_to menggunakan metode format dari kelas str bawaan untuk mengumpulkan float menjadi string yang mewakili float dengan jumlah desimal yang diperlukan, dan kemudian menerapkan fungsi built-in eval ke string float bulat untuk kembali. ke tipe numerik float.
Fungsi is_close hanya menerapkan persyaratan sederhana untuk float yang dibulatkan ke atas.
Memperbarui:
Seperti yang disarankan oleh @stepehjfox, cara yang lebih bersih untuk membangun fungsi rount_to menghindari "eval" menggunakan pemformatan bersarang :
Mengikuti ide yang sama, kode ini bahkan bisa lebih sederhana menggunakan f-string baru yang hebat (Python 3.6+):
Jadi, kita bahkan bisa membungkusnya semua dalam satu fungsi 'is_close' yang sederhana dan bersih :
sumber
eval()
untuk mendapatkan pemformatan parameter. Sesuatu sepertireturn '{:.{precision}f'.format(float_num, precision=decimal_precision)
harus melakukannyareturn '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Dalam hal kesalahan absolut, Anda bisa mengeceknya
Beberapa informasi tentang mengapa float bertindak aneh dengan Python https://youtu.be/v4HhvoNLILk?t=1129
Anda juga dapat menggunakan math.isclose untuk kesalahan relatif
sumber