Apa yang salah dengan menggunakan == untuk membandingkan pelampung di Jawa?


177

Menurut halaman java.sun ini == adalah operator perbandingan kesetaraan untuk angka floating point di Jawa.

Namun, ketika saya mengetikkan kode ini:

if(sectionID == currentSectionID)

ke editor saya dan menjalankan analisis statis, saya mendapatkan: "Nilai titik mengambang JAVA0078 dibandingkan dengan =="

Apa yang salah dengan menggunakan ==untuk membandingkan nilai floating point? Apa cara yang benar untuk melakukannya? 


29
Karena membandingkan float dengan == bermasalah, tidak bijaksana untuk menggunakannya sebagai ID; nama-nama dalam kode contoh Anda menunjukkan itulah yang Anda lakukan; bilangan bulat panjang (long) lebih disukai, dan standar de facto untuk ID.
Carl Manaster


4
Ya, apakah itu hanya contoh acak atau apakah Anda benar-benar menggunakan pelampung sebagai ID? Apakah ada alasan?
Per Wiklander

5
"untuk bidang float, gunakan metode Float.compare; dan untuk bidang ganda, gunakan Double.compare. Perlakuan khusus bidang float dan ganda dibuat perlu dengan adanya Float.NaN, -0.0f dan konstanta ganda analog; lihat dokumentasi Float.equals untuk detailnya. " (Joshua Bloch: Java Efektif)
lbalazscs

Jawaban:


211

cara yang benar untuk menguji mengapung untuk 'kesetaraan' adalah:

if(Math.abs(sectionID - currentSectionID) < epsilon)

di mana epsilon adalah angka yang sangat kecil seperti 0,00000001, tergantung pada presisi yang diinginkan.


26
Lihat tautan dalam jawaban yang diterima ( cygnus-software.com/papers/comparingfloats/comparingfloats.htm ) untuk alasan mengapa epsilon yang diperbaiki tidak selalu merupakan ide yang baik. Secara khusus, karena nilai dalam float yang dibandingkan menjadi besar (atau kecil), epsilon tidak lagi sesuai. (Menggunakan epsilon baik-baik saja jika Anda tahu nilai float Anda semuanya relatif masuk akal.)
PT

1
@ PT Bisakah dia mengalikan epsilon dengan satu angka dan mengubah fungsi if(Math.abs(sectionID - currentSectionID) < epsilon*sectionIDuntuk mengatasi masalah itu?
Antusiasme

3
Ini mungkin jawaban terbaik sejauh ini, tetapi masih salah. Dari mana Anda mendapatkan epsilon?
Michael Piefel

1
@MichaelPiefel sudah mengatakan: "tergantung pada presisi yang diinginkan". Mengapung menurut sifatnya adalah semacam nilai fisik: Anda hanya tertarik pada sejumlah posisi terbatas tergantung pada ketidaktepatan total, perbedaan apa pun di luar yang dianggap diperdebatkan.
ivan_pozdeev

Tetapi OP benar-benar hanya ingin menguji kesetaraan, dan karena ini diketahui tidak dapat diandalkan, harus menggunakan metode yang berbeda. Namun, saya tidak mengerti dia tahu apa "presisi yang diinginkan" nya bahkan; jadi jika semua yang Anda inginkan adalah tes kesetaraan yang lebih andal, pertanyaannya tetap: Dari mana Anda mendapatkan epsilon? Saya mengusulkan menggunakan Math.ulp()jawaban saya untuk pertanyaan ini.
Michael Piefel

53

Nilai floating point dapat dimatikan sedikit, sehingga nilai tersebut mungkin tidak sama persis. Misalnya, mengatur pelampung ke "6.1" dan kemudian mencetaknya lagi, Anda mungkin mendapatkan nilai yang dilaporkan seperti "6.099999904632568359375". Ini adalah dasar cara mengapung bekerja; Oleh karena itu, Anda tidak ingin membandingkannya menggunakan persamaan, melainkan perbandingan dalam rentang, yaitu, jika perbedaan float ke angka yang ingin Anda bandingkan dengan kurang dari nilai absolut tertentu.

Ini artikel di Register memberikan gambaran yang baik tentang mengapa hal ini terjadi; bacaan yang bermanfaat dan menarik.


@kevindtimm: jadi Anda akan melakukan tes kesetaraan Anda seperti demikian maka jika (angka == 6.099999904632568359375) setiap saat Anda ingin tahu angka sama dengan 6.1 ... Ya Anda benar ... semua yang ada di komputer sangat deterministik, Hanya saja perkiraan yang digunakan untuk mengapung bersifat kontra-intuitif ketika melakukan masalah matematika.
Newtopian

Nilai floating point hanya tidak pasti pada perangkat keras yang sangat spesifik .
Stuart P. Bentley

1
@ Segtu saya bisa saja salah, tapi saya tidak berpikir bug FDIV adalah non-deterministik. Jawaban yang diberikan oleh perangkat keras tidak sesuai dengan spesifikasi, tetapi mereka deterministik, karena perhitungan yang sama selalu menghasilkan hasil yang salah yang sama
Gravity

@ Gravity Anda dapat berargumen bahwa perilaku apa pun adalah deterministik mengingat serangkaian peringatan tertentu.
Stuart P. Bentley

Floating point nilai tidak tepat. Setiap nilai floating point persis seperti apa adanya. Apa yang mungkin tidak tepat adalah hasil dari perhitungan floating point . Tapi waspadalah! Ketika Anda melihat sesuatu seperti 0,1 dalam suatu program, itu bukan nilai floating point. Itu adalah floating point literal --- string yang dikompilasi oleh kompiler menjadi nilai floating point dengan melakukan perhitungan .
Solomon Slow

22

Hanya untuk memberikan alasan di balik apa yang orang lain katakan.

Representasi biner dari float agak mengganggu.

Dalam biner, sebagian besar programmer mengetahui korelasi antara 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d

Yah itu bekerja sebaliknya juga.

.1b = .5d, .01b = .25d, .001b = .125, ...

Masalahnya adalah bahwa tidak ada cara yang tepat untuk mewakili angka paling desimal seperti .1, .2, .3, dll. Yang dapat Anda lakukan hanyalah perkiraan dalam biner. Sistem melakukan pembulatan sedikit fudge ketika angka-angka dicetak sehingga menampilkan .1 bukannya .10000000000001 atau .999999999999 (yang mungkin sama sedekat mungkin dengan representasi yang disimpan seperti .1)

Sunting dari komentar: Alasan ini masalah adalah harapan kami. Kami sepenuhnya berharap 2/3 untuk dikaburkan pada titik tertentu ketika kami mengonversinya menjadi desimal, baik .7 atau .67 atau .666667 .. Tetapi kami tidak secara otomatis berharap .1 akan dibulatkan dengan cara yang sama seperti 2/3 --dan itulah yang terjadi.

Ngomong-ngomong, jika Anda penasaran nomor yang disimpan secara internal adalah representasi biner murni menggunakan "Notasi Ilmiah" biner. Jadi jika Anda mengatakan untuk menyimpan angka desimal 10.75d, itu akan menyimpan 1010b untuk angka 10, dan .11b untuk angka desimal. Jadi itu akan menyimpan 0,101011 kemudian menyimpan beberapa bit pada akhirnya untuk mengatakan: Pindahkan titik desimal empat tempat ke kanan.

(Meskipun secara teknis ini bukan lagi titik desimal, sekarang menjadi titik biner, tetapi terminologi itu tidak akan membuat hal-hal lebih dimengerti bagi kebanyakan orang yang akan menemukan jawaban ini dari penggunaan apa pun.)


1
@ Mat K - um, bukan titik tetap; jika Anda "menyimpan beberapa bit di bagian akhir untuk mengatakan pindahkan titik desimal [N] bit ke kanan", itu adalah floating point. Titik tetap mengambil posisi titik radix menjadi, baik, tetap. Juga, secara umum, karena menggeser titik binamal (?) Selalu dapat dibuat untuk meninggalkan Anda dengan '1' di posisi paling kiri, Anda akan menemukan beberapa sistem yang menghilangkan terkemuka '1', mencurahkan ruang sehingga dibebaskan (1 bit!) untuk memperluas jangkauan eksponen.
JustJeff

Masalahnya tidak ada hubungannya dengan representasi biner vs desimal. Dengan floating-point desimal, Anda masih memiliki hal-hal seperti (1/3) * 3 == 0,999999999999999999999999999999.
dan04

2
@ dan04 ya, karena 1/3 tidak memiliki representasi desimal ATAU biner, ia memiliki representasi trinary dan akan dikonversi dengan benar seperti itu :). Angka-angka yang saya daftarkan (.1, .25, dll) semuanya memiliki representasi desimal sempurna tetapi tidak ada representasi biner - dan orang-orang terbiasa dengan mereka yang memiliki representasi "tepat". BCD akan menanganinya dengan sempurna. Itulah bedanya.
Bill K

1
Ini seharusnya memiliki lebih banyak upvotes, karena ini menggambarkan masalah NYATA di balik masalah ini.
Levite

19

Apa yang salah dengan menggunakan == untuk membandingkan nilai floating point?

Karena itu tidak benar 0.1 + 0.2 == 0.3


7
bagaimana Float.compare(0.1f+0.2f, 0.3f) == 0?
Aquarius Power

0,1 f + 0,2 f == 0,3 d tetapi 0,1 d + 0,2 d! = 0,3 d. Secara default, 0,1 + 0,2 adalah ganda. 0,3 adalah ganda juga.
burnabyRails

12

Saya pikir ada banyak kebingungan di sekitar mengapung (dan ganda), ada baiknya untuk membersihkannya.

  1. Tidak ada yang salah secara inheren dalam menggunakan float sebagai ID di JVM yang sesuai standar [*]. Jika Anda cukup mengatur float ID ke x, jangan lakukan apa-apa dengan itu (yaitu tidak ada aritmatika) dan kemudian tes untuk y == x, Anda akan baik-baik saja. Juga tidak ada yang salah dalam menggunakannya sebagai kunci dalam HashMap. Apa yang tidak dapat Anda lakukan adalah mengasumsikan persamaan x == (x - y) + y, dll. Karena ini, orang biasanya menggunakan tipe bilangan bulat sebagai ID, dan Anda dapat mengamati bahwa sebagian besar orang di sini ditunda oleh kode ini, jadi untuk alasan praktis, lebih baik mematuhi konvensi . Perhatikan bahwa ada banyak doublenilai yang berbeda dengan yang lama values, jadi Anda tidak mendapatkan apa pun dengan menggunakan double. Juga, menghasilkan "ID tersedia berikutnya" bisa rumit dengan ganda dan membutuhkan pengetahuan tentang aritmatika titik-mengambang. Tidak sepadan dengan masalahnya.

  2. Di sisi lain, mengandalkan kesetaraan numerik dari hasil dua perhitungan yang setara secara matematis adalah berisiko. Ini karena kesalahan pembulatan dan kehilangan presisi ketika mengkonversi dari representasi desimal ke biner. Ini telah dibahas sampai mati pada SO.

[*] Ketika saya mengatakan "JVM yang sesuai standar" saya ingin mengecualikan implementasi JVM yang rusak otak. Lihat ini .


Ketika menggunakan float sebagai ID, kita harus berhati-hati untuk memastikan bahwa mereka dibandingkan dengan menggunakan ==daripada equals, atau yang lain memastikan bahwa tidak ada float yang membandingkan tidak sama dengan dirinya sendiri akan disimpan dalam sebuah tabel. Jika tidak, sebuah program yang mencoba menghitung misalnya berapa hasil unik yang dapat dihasilkan dari ekspresi ketika diberi makan berbagai input dapat menganggap setiap nilai NaN sebagai unik.
supercat

Di atas mengacu pada Float, bukan untuk float.
quant_dev

Apa yang dibicarakan Float? Jika seseorang mencoba membangun tabel floatnilai unik dan membandingkannya dengan ==, aturan perbandingan IEEE-754 yang mengerikan akan menghasilkan tabel yang dibanjiri dengan NaNnilai.
supercat

floattipe tidak memiliki equalsmetode.
quant_dev

Ah - saya tidak bermaksud equalsmetode contoh, tetapi metode statis (saya pikir di dalam Floatkelas) yang membandingkan dua nilai tipe float.
supercat

9

Ini adalah masalah yang tidak spesifik untuk java. Menggunakan == untuk membandingkan dua float / ganda / angka desimal apa pun berpotensi menyebabkan masalah karena cara mereka disimpan. Pelampung presisi tunggal (sesuai standar IEEE 754) memiliki 32 bit, didistribusikan sebagai berikut:

1 bit - Masuk (0 = positif, 1 = negatif)
8 bit - Eksponen (khusus (bias-127) representasi x dalam 2 ^ x)
23 bit - Mantisa. Angka aktual yang disimpan.

Mantera inilah yang menyebabkan masalah. Ini agak seperti notasi ilmiah, hanya angka dalam basis 2 (biner) yang terlihat seperti 1,110011 x 2 ^ 5 atau yang serupa. Namun dalam biner, 1 pertama selalu 1 (kecuali untuk representasi 0)

Oleh karena itu, untuk menghemat sedikit ruang memori (pun intended), IEEE memutuskan bahwa 1 harus diasumsikan. Misalnya, mantisa 1011 benar-benar adalah 1,1011.

Ini dapat menyebabkan beberapa masalah dengan perbandingan, terutama dengan 0 karena 0 tidak mungkin diwakili secara tepat dalam float. Ini adalah alasan utama == tidak disarankan, di samping masalah matematika floating point yang dijelaskan oleh jawaban lain.

Java memiliki masalah unik karena bahasanya universal di banyak platform berbeda, yang masing-masing bisa memiliki format float unik itu sendiri. Itu membuatnya lebih penting untuk menghindari ==.

Cara yang tepat untuk membandingkan dua pelampung (bukan bahasa khusus Anda) untuk kesetaraan adalah sebagai berikut:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

di mana ACCEPTABLE_ERROR adalah #defined atau konstanta lainnya sama dengan 0,000000001 atau presisi apa pun yang diperlukan, seperti yang sudah disebutkan Victor.

Beberapa bahasa memiliki fungsi ini atau konstanta bawaan ini, tetapi umumnya ini adalah kebiasaan yang baik untuk dilakukan.


3
Java memiliki perilaku yang pasti untuk float. Itu tidak tergantung platform.
Yishai

9

Sampai hari ini, cara cepat & mudah untuk melakukannya adalah:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

Namun, dokumen tidak secara jelas menentukan nilai selisih margin (sebuah epsilon dari jawaban @Victor) yang selalu ada dalam perhitungan pada float, tetapi harus menjadi sesuatu yang masuk akal karena merupakan bagian dari perpustakaan bahasa standar.

Namun jika dibutuhkan ketelitian yang lebih tinggi atau disesuaikan

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

adalah pilihan solusi lain.


1
Dokumen yang Anda tautkan menyatakan "nilai 0 jika f1 secara numerik sama dengan f2" yang membuatnya sama dengan melakukan (sectionId == currentSectionId)yang tidak akurat untuk floating point. metode epsilon adalah pendekatan yang lebih baik, yang ada di jawaban ini: stackoverflow.com/a/1088271/4212710
typoerrpr

8

Nilai titik berbusa tidak dapat diandalkan, karena kesalahan pembulatan.

Karena itu, mereka mungkin tidak boleh digunakan sebagai nilai kunci, seperti sectionID. Gunakan bilangan bulat sebagai gantinya, atau longjika inttidak mengandung nilai yang cukup mungkin.


2
Sepakat. Mengingat bahwa ini adalah ID, tidak ada alasan untuk mempersulit hal-hal dengan aritmatika floating point.
Yohnny

2
Atau lama. Bergantung pada berapa banyak ID unik yang dihasilkan di masa depan, int mungkin tidak cukup besar.
Wayne Hartman

Seberapa tepat ganda dibandingkan dengan float?
Arvindh Mani

1
@ ArvindhMani doublejauh lebih tepat, tetapi mereka juga nilai floating point, jadi jawaban saya dimaksudkan untuk menyertakan keduanya floatdan double.
Eric Wilson

7

Selain jawaban sebelumnya, Anda harus menyadari bahwa ada perilaku aneh yang terkait dengan -0.0fdan +0.0f(mereka ==tetapi tidak equals) dan Float.NaN(itu equalstapi tidak ==) (harapan saya benar - argh, jangan lakukan itu!).

Sunting: Mari kita periksa!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

Selamat datang di IEEE / 754.


Jika sesuatu ==, maka mereka identik ke bit. Bagaimana mungkin mereka tidak sama dengan ()? Mungkin Anda memilikinya mundur?
mkb

@ Matt NaN spesial. Double.isNaN (double x) di Java sebenarnya diimplementasikan sebagai {return x! = X; } ...
quant_dev

1
Dengan float, ==tidak berarti angka "identik dengan bit" (angka yang sama dapat diwakili dengan pola bit yang berbeda, meskipun hanya satu dari mereka yang dinormalisasi bentuk). Juga, -0.0fdan 0.0fdiwakili oleh pola bit yang berbeda (bit tanda berbeda), tetapi bandingkan sebagai sama dengan ==(tetapi tidak dengan equals). Asumsi Anda bahwa ==perbandingan bitwise, secara umum, salah.
Pavel Minaev


5

Anda dapat menggunakan Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

1
Anda berada di jalur yang benar. floatToIntBits () adalah cara yang tepat untuk digunakan, tetapi akan lebih mudah untuk hanya menggunakan fungsi Float's built in equals (). Lihat di sini: stackoverflow.com/a/3668105/2066079 . Anda dapat melihat bahwa default sama dengan () menggunakan floatToIntBits secara internal.
dberm22

1
Ya jika itu adalah benda Float. Anda dapat menggunakan persamaan di atas untuk primitif.
aamadmi

4

Pertama-tama, apakah mereka mengambang atau mengambang? Jika salah satunya adalah Float, Anda harus menggunakan metode equals (). Juga, mungkin yang terbaik untuk menggunakan metode statis Float.compare.


4

Berikut ini secara otomatis menggunakan presisi terbaik:

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

Tentu saja, Anda dapat memilih lebih dari 5 ULP ('unit di tempat terakhir').

Jika Anda masuk ke pustaka Apache Commons, Precisionkelas memiliki compareTo()dan equals()dengan epsilon dan ULP.


ketika mengubah float menjadi double, metode ini tidak berfungsi seperti isDoubleEqual (0,1 + 0,2-0,3, 0,0) == false
hychou

Tampaknya Anda perlu lebih banyak seperti 10_299_299_299_299_L sebagai faktor untuk doublemenutupi ini.
Michael Piefel

3

Anda mungkin ingin menjadi ==, tetapi 123.4444444444443! = 123.444444444444242



2

Dua perhitungan berbeda yang menghasilkan bilangan real yang sama tidak harus menghasilkan bilangan floating point yang sama. Orang yang menggunakan == untuk membandingkan hasil perhitungan biasanya pada akhirnya terkejut oleh hal ini, sehingga peringatan membantu menandai apa yang mungkin menjadi bug yang halus dan sulit untuk diperbanyak.


2

Apakah Anda berurusan dengan kode outsourcing yang akan menggunakan float untuk hal-hal yang bernama sectionID dan currentSectionID? Hanya penasaran.

@ Bill K: "Representasi biner float agak mengganggu." Bagaimana? Bagaimana Anda melakukannya dengan lebih baik? Ada angka-angka tertentu yang tidak dapat direpresentasikan dalam basis apa pun dengan benar, karena tidak pernah berakhir. Pi adalah contoh yang bagus. Anda hanya bisa memperkirakannya. Jika Anda memiliki solusi yang lebih baik, hubungi Intel.


1

Seperti disebutkan dalam jawaban lain, ganda dapat memiliki penyimpangan kecil. Dan Anda dapat menulis metode Anda sendiri untuk membandingkannya menggunakan deviasi yang "dapat diterima". Namun ...

Ada kelas apache untuk membandingkan ganda: org.apache.commons.math3.util.Precision

Ini berisi beberapa konstanta yang menarik: SAFE_MINdan EPSILON, yang merupakan penyimpangan maksimum yang mungkin dari operasi aritmatika sederhana.

Ini juga menyediakan metode yang diperlukan untuk membandingkan, menyamakan atau membulatkan ganda. (menggunakan ulp atau deviasi absolut)


1

Dalam satu jawaban yang bisa saya katakan, Anda harus menggunakan:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

Untuk membuat Anda belajar lebih banyak tentang penggunaan operator terkait dengan benar, saya menguraikan beberapa kasus di sini: Secara umum, ada tiga cara untuk menguji string di Jawa. Anda bisa menggunakan ==, .equals (), atau Objects.equals ().

Bagaimana mereka berbeda? == tes untuk kualitas referensi dalam string yang berarti mencari tahu apakah kedua benda itu sama. Di sisi lain, .equals () menguji apakah kedua string memiliki nilai yang sama secara logis. Akhirnya, Objects.equals () menguji setiap null di dua string kemudian menentukan apakah akan memanggil .equals ().

Operator ideal untuk digunakan

Nah ini telah menjadi subyek banyak perdebatan karena masing-masing dari tiga operator memiliki kekuatan dan kelemahan yang unik. Contoh, == sering merupakan opsi yang disukai ketika membandingkan referensi objek, tetapi ada beberapa kasus di mana ia tampaknya membandingkan nilai string juga.

Namun, apa yang Anda dapatkan adalah nilai jatuh karena Java menciptakan ilusi bahwa Anda membandingkan nilai tetapi dalam arti sebenarnya Anda tidak. Pertimbangkan dua kasus di bawah ini:

Kasus 1:

String a="Test";
String b="Test";
if(a==b) ===> true

Kasus 2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

Jadi, lebih baik menggunakan setiap operator saat menguji atribut khusus yang dirancang untuknya. Tetapi dalam hampir semua kasus, Objects.equals () adalah operator yang lebih universal sehingga pengalaman pengembang web memilih untuk itu.

Di sini Anda bisa mendapatkan detail lebih lanjut: http://fluentthemes.com/use-compare-strings-java/


-2

Cara yang benar adalah

java.lang.Float.compare(float1, float2)

7
Float.compare (float1, float2) mengembalikan sebuah int, jadi ia tidak bisa digunakan sebagai ganti float1 == float2 dalam kondisi if. Selain itu, itu tidak benar-benar menyelesaikan masalah mendasar yang merujuk pada peringatan ini - bahwa jika float adalah hasil perhitungan numerik, float1! = Float2 dapat terjadi hanya karena kesalahan pembulatan.
quant_dev

1
benar, Anda tidak dapat menyalin rekatkan, Anda harus memeriksa dokumen terlebih dahulu.
Eric

2
Apa yang dapat Anda lakukan daripada float1 == float2 adalah Float.compare (float1, float2) == 0.
deterb

29
Ini tidak membeli apa pun - Anda masih mendapatkanFloat.compare(1.1 + 2.2, 3.3) != 0
Pavel Minaev

-2

Salah satu cara untuk mengurangi kesalahan pembulatan adalah menggunakan double daripada float. Ini tidak akan membuat masalah hilang, tetapi mengurangi jumlah kesalahan dalam program Anda dan float hampir tidak pernah menjadi pilihan terbaik. MENURUT OPINI SAYA.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.